<div class="content-section introduction">
    <div class="feature-intro">
        <h1>Table</h1>
        <p>Table displays data in tabular format.</p>
    </div>
    <app-demoActions github="table" stackblitz="tabledoc-demo"></app-demoActions>
</div>

<div class="content-section implementation">
    <div class="card">
        <p-table #dt [value]="customers" [(selection)]="selectedCustomers" dataKey="id" styleClass="p-datatable-customers" [rowHover]="true"
            [rows]="10" [showCurrentPageReport]="true" [rowsPerPageOptions]="[10,25,50]" [loading]="loading"  responsiveLayout="scroll"
            [paginator]="true" currentPageReportTemplate="Showing {first} to {last} of {totalRecords} entries"
            [filterDelay]="0" [globalFilterFields]="['name','country.name','representative.name','status']">
            <ng-template pTemplate="caption">
                <div class="table-header">
                    List of Customers
                    <span class="p-input-icon-left">
                        <i class="pi pi-search"></i>
                        <input pInputText type="text" (input)="dt.filterGlobal($event.target.value, 'contains')" placeholder="Global Search" />
                    </span>
                </div>
            </ng-template>
            <ng-template pTemplate="header">
                <tr>
                    <th style="width: 3rem">
                        <p-tableHeaderCheckbox></p-tableHeaderCheckbox>
                    </th>
                    <th pSortableColumn="name">
                        <div class="flex justify-content-between align-items-center">
                            Name
                            <p-sortIcon field="name"></p-sortIcon>
                            <p-columnFilter type="text" field="name" display="menu" class="ml-auto"></p-columnFilter>
                        </div>
                    </th>
                    <th pSortableColumn="country.name">
                        <div class="flex justify-content-between align-items-center">
                            Country
                            <p-sortIcon field="country.name"></p-sortIcon>
                            <p-columnFilter type="text" field="country.name" display="menu" class="ml-auto"></p-columnFilter>
                        </div>
                    </th>
                    <th pSortableColumn="representative.name">
                        <div class="flex justify-content-between align-items-center">
                            Agent
                            <p-sortIcon field="representative.name"></p-sortIcon>
                            <p-columnFilter field="representative" matchMode="in" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false" class="ml-auto">
                                <ng-template pTemplate="header">
                                    <div class="px-3 pt-3 pb-0">
                                        <span class="font-bold">Agent Picker</span>
                                    </div>
                                </ng-template>
                                <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                                    <p-multiSelect [ngModel]="value" [options]="representatives" placeholder="Any" (onChange)="filter($event.value)" optionLabel="name">
                                        <ng-template let-option pTemplate="item">
                                            <div class="p-multiselect-representative-option">
                                                <img [alt]="option.label" src="assets/showcase/images/demo/avatar/{{option.image}}" width="32" style="vertical-align: middle" />
                                                <span class="ml-1">{{option.name}}</span>
                                            </div>
                                        </ng-template>
                                    </p-multiSelect>
                                </ng-template>
                            </p-columnFilter>
                        </div>
                    </th>
                    <th pSortableColumn="date">
                        <div class="flex justify-content-between align-items-center">
                            Date
                            <p-sortIcon field="date"></p-sortIcon>
                            <p-columnFilter type="date" field="date" display="menu" class="ml-auto"></p-columnFilter>
                        </div>
                    </th>
                    <th pSortableColumn="balance">
                        <div class="flex justify-content-between align-items-center">
                            Balance
                            <p-sortIcon field="balance"></p-sortIcon>
                            <p-columnFilter type="numeric" field="balance" display="menu" currency="USD" class="ml-auto"></p-columnFilter>
                        </div>
                    </th>
                    <th pSortableColumn="status">
                        <div class="flex justify-content-between align-items-center">
                            Status
                            <p-sortIcon field="status"></p-sortIcon>
                            <p-columnFilter field="status" matchMode="equals" display="menu" class="ml-auto">
                                <ng-template pTemplate="filter" let-value let-filter="filterCallback">
                                    <p-dropdown [ngModel]="value" [options]="statuses" (onChange)="filter($event.value)" placeholder="Any">
                                        <ng-template let-option pTemplate="item">
                                            <span [class]="'customer-badge status-' + option.value">{{option.label}}</span>
                                        </ng-template>
                                    </p-dropdown>
                                </ng-template>
                            </p-columnFilter>
                        </div>
                    </th>
                    <th pSortableColumn="activity">
                        <div class="flex justify-content-between align-items-center">
                            Activity
                            <p-sortIcon field="activity"></p-sortIcon>
                            <p-columnFilter field="activity" matchMode="between" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false" class="ml-auto">
                                <ng-template pTemplate="filter" let-filter="filterCallback">
                                    <p-slider [ngModel]="activityValues" [range]="true" (onSlideEnd)="filter($event.values)" styleClass="m-3"></p-slider>
                                    <div class="flex align-items-center justify-content-between px-2">
                                        <span>{{activityValues[0]}}</span>
                                        <span>{{activityValues[1]}}</span>
                                    </div>
                                </ng-template>
                            </p-columnFilter>
                        </div>
                    </th>
                    <th style="width: 8rem"></th>
                </tr>
            </ng-template>
            <ng-template pTemplate="body" let-customer>
                <tr class="p-selectable-row">
                    <td>
                        <p-tableCheckbox [value]="customer"></p-tableCheckbox>
                    </td>
                    <td>
                        <span class="p-column-title">Name</span>
                        {{customer.name}}
                    </td>
                    <td>
                        <span class="p-column-title">Country</span>
                        <img src="assets/showcase/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" width="30">
                        <span class="image-text">{{customer.country.name}}</span>
                    </td>
                    <td>
                        <span class="p-column-title">Representative</span>
                        <img [alt]="customer.representative.name" src="assets/showcase/images/demo/avatar/{{customer.representative.image}}" width="32" style="vertical-align: middle" />
                        <span class="image-text">{{customer.representative.name}}</span>
                    </td>
                    <td>
                        <span class="p-column-title">Date</span>
                        {{customer.date | date: 'MM/dd/yyyy'}}
                    </td>
                    <td>
                        <span class="p-column-title">Balance</span>
                        {{customer.balance | currency:'USD':'symbol'}}
                    </td>
                    <td>
                        <span class="p-column-title">Status</span>
                        <span [class]="'customer-badge status-' + customer.status">{{customer.status}}</span>
                    </td>
                    <td>
                        <span class="p-column-title">Activity</span>
                        <p-progressBar [value]="customer.activity" [showValue]="false"></p-progressBar>
                    </td>
                    <td style="text-align: center">
                        <button pButton type="button" class="p-button-secondary" icon="pi pi-cog"></button>
                    </td>
                </tr>
            </ng-template>
            <ng-template pTemplate="emptymessage">
                <tr>
                    <td colspan="8">No customers found.</td>
                </tr>
            </ng-template>
        </p-table>
    </div>
</div>

<div class="content-section documentation">
    <p-tabView>
        <p-tabPanel header="Documentation">
            <h5>Import</h5>
<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123;TableModule&#125; from 'primeng/table';
</app-code>

            <h5>Getting Started</h5>
            <p>Table requires a value as an array of objects and templates for the presentation. Throughout the samples, a car interface having a
            vin, brand, year and color properties is used to define an object to be displayed by the table. Cars are loaded by a CarService that
            connects to a server to fetch the data.</p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export interface Car &#123;
    vin;
    year;
    brand;
    color;
&#125;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123; HttpClient &#125; from '@angular/common/http';
import &#123; Injectable &#125; from '@angular/core';

import &#123; Car &#125; from '../domain/car';

@Injectable()
export class CarService &#123;

    constructor(private http: HttpClient) &#123;&#125;

    getCarsSmall() &#123;
        return this.http.get('/showcase/resources/data/cars-small.json')
                    .toPromise()
                    .then(res => &lt;Car[]&gt; res.data)
                    .then(data => &#123; return data; &#125;);
    &#125;
&#125;
</app-code>

            <p>Following sample has a table of 4 columns and retrieves the data from a service on ngOnInit.</p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableDemo implements OnInit &#123;

    cars: Car[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    &#125;
&#125;
</app-code>

        <p>List of cars are bound to the value property whereas <i>header</i> and <i>body</i> templates are used to define the content of these sections.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="cars"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th&gt;Vin&lt;/th&gt;
            &lt;th&gt;Year&lt;/th&gt;
            &lt;th&gt;Brand&lt;/th&gt;
            &lt;th&gt;Color&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-car&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;car.vin&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.year&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.brand&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.color&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

        <h5>Dynamic Columns</h5>
        <p>Instead of configuring columns one by one, a simple ngFor can be used to implement dynamic columns. cols property below is an array of objects that represent a column.
            The only property that the table component uses is field, and the rest of the properties like <i>header</i> depend on your choice.
        </p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class DynamicColumnsDemo implements OnInit &#123;

    cars: Car[];

    cols: any[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.cols = [
            &#123; field: 'vin', header: 'Vin' &#125;,
            &#123; field: 'year', header: 'Year' &#125;,
            &#123; field: 'brand', header: 'Brand' &#125;,
            &#123; field: 'color', header: 'Color' &#125;
        ];
    &#125;
&#125;
</app-code>

        <p>There are two ways to render dynamic columns. Since the cols property is in the scope of component, you can simply bind it to the ngFor directive to generate the structure.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="cars"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of cols"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-car&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of cols"&gt;
                    &#123;&#123;car[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>The other alternative is binding the cols array to the <i>columns</i> property and then defining a template variable to access within your templates.
                There are 3 cases where this is required which are: csv export, reorderable columns, and global filtering without the <i>globalFilterFields</i> property.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-car let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                    &#123;&#123;car[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>Tip: Use ngSwitch to customize the column content per dynamic column.</p>

            <h5>Table Layout</h5>
            <p>For performance reasons, default table-layout is fixed meaning the cell widths do not depend on their content. If you require cells to scale based on their contents,
                set <i>autoLayout</i> property to true. Note that Scrollable and/or Resizable tables do not support auto layout due to technical reasons.
            </p>

            <h5>Templates</h5>
            <p>Table is a template driven component with named templates such as header and body that we've used so far. Templates grant a great level of customization and flexibility
                where you have total control over the presentation while table handles the features such as paging, sorting, filtering and more. This speeds up development without sacrificing
                flexibility. Here is the full list of available templates.</p>

            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Parameters</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>caption</td>
                            <td>-</td>
                            <td>Caption content of the table.</td>
                        </tr>
                        <tr>
                            <td>header</td>
                            <td>$implicit: Columns</td>
                            <td>Content of the thead element.</td>
                        </tr>
                        <tr>
                            <td>headergrouped</td>
                            <td>$implicit: Columns</td>
                            <td>Content of the thead element.</td>
                        </tr>
                        <tr>
                            <td>body</td>
                            <td>$implicit: Data of the row <br>
                                rowIndex: Index of the row <br>
                                columns: Columns collection <br>
                                expanded: Whether the row is expanded <br>
                                rowgroup: Wheter the row is rowgroup <br>
                                rowspan: Row span of the rowgroup
                            </td>
                            <td>Content of the tbody element.</td>
                        </tr>
                        <tr>
                            <td>footer</td>
                            <td>$implicit: Columns</td>
                            <td>Content of the tfoot element.</td>
                        </tr>
                        <tr>
                            <td>footergrouped</td>
                            <td>$implicit: Columns</td>
                            <td>Content of the tfoot element.</td>
                        </tr>
                        <tr>
                            <td>summary</td>
                            <td>-</td>
                            <td>Summary section to display below the table.</td>
                        </tr>
                        <tr>
                            <td>rowexpansion</td>
                            <td>$implicit: Data of the row <br>
                                rowIndex: Index of the row <br>
                                columns: Columns collection <br>
                            </td>
                            <td>Content of an extended row.</td>
                        </tr>
                        <tr>
                            <td>frozenbody</td>
                            <td>$implicit: Data of the row <br>
                                rowIndex: Index of the row <br>
                                columns: Columns collection <br>
                            </td>
                            <td>Content of the tbody element in frozen side.</td>
                        </tr>
                        <tr>
                            <td>frozenrowexpansion</td>
                            <td>$implicit: Data of the row <br>
                                rowIndex: Index of the row <br>
                                columns: Columns collection <br>
                            </td>
                            <td>Content of an extended row in frozen side.</td>
                        </tr>
                        <tr>
                            <td>groupheader</td>
                            <td>$implicit: Data of the row <br>
                                rowIndex: Index of the row <br>
                                columns: Columns collection <br>
                            </td>
                            <td>Header of the grouped rows.</td>
                        </tr>
                        <tr>
                            <td>groupfooter</td>
                            <td>$implicit: Data of the row <br>
                                rowIndex: Index of the row <br>
                                columns: Columns collection <br>
                            </td>
                            <td>Footer of the grouped rows.</td>
                        </tr>
                        <tr>
                            <td>emptymessage</td>
                            <td>$implicit: Columns <br />
                                frozen: Whether the body belongs to the frozen part of the table.</td>
                            <td>Content to display when there is no value to display.</td>
                        </tr>
                        <tr>
                            <td>paginatorleft</td>
                            <td>state: $implicit
                                state.page: Current page<br />
                                state.pageCount: Total page count<br />
                                state.rows: Rows per page<br />
                                state.first: Index of the first records<br />
                                state.totalRecords: Number of total records<br />
                            </td>
                            <td>Custom content for the left section of the paginator.</td>
                        </tr>
                        <tr>
                            <td>paginatorright</td>
                            <td>state: $implicit
                                state.page: Current page<br />
                                state.pageCount: Total page count<br />
                                state.rows: Rows per page<br />
                                state.first: Index of the first records<br />
                                state.totalRecords: Number of total records<br />
                            </td>
                            <td>Custom content for the right section of the paginator.</td>
                        </tr>
                        <tr>
                            <td>loadingbody</td>
                            <td>columns: Columns collection <br>
                            </td>
                            <td>Content of the tbody element to show when data is being loaded in virtual scroll mode.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Change Detection</h5>
            <p>Table may need to be aware of changes in its value in some cases such as reapplying sort. For the sake of performance, this is only done when the reference of the value changes, meaning a
                setter is used instead of ngDoCheck/IterableDiffers which can reduce performance. So when you manipulate the value such as removing or adding an item, instead of using array methods such as push and splice, instead
                create a new array reference using a spread operator or similar.
            </p>

            <h5>Sections</h5>
            <p>Table offers various templates to display additional information about the data such as a <i>caption</i> or <i>summary</i>.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars"&gt;
    &lt;ng-template pTemplate="caption"&gt;
        List of Cars
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
            &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="footer" let-columns&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
            &#123;&#123;col.header&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="summary"&gt;
        There are &#123;&#123;cars?.length&#125;&#125; cars
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/sections']">live example.</a></p>

            <h5>Column Grouping</h5>
            <p>Columns can easily be grouped using templating. Let's start with sample data of sales of brands per year. Note that if you use Scrollable table should be used headergrouped and footergrouped templates instead of header and footer templates.</p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableColGroupDemo implements OnInit &#123;

    sales: any[];

    ngOnInit() &#123;
        this.sales = [
            &#123; brand: 'Apple', lastYearSale: '51%', thisYearSale: '40%', lastYearProfit: '$54,406.00', thisYearProfit: '$43,342' &#125;,
            &#123; brand: 'Samsung', lastYearSale: '83%', thisYearSale: '96%', lastYearProfit: '$423,132', thisYearProfit: '$312,122' &#125;,
            &#123; brand: 'Microsoft', lastYearSale: '38%', thisYearSale: '5%', lastYearProfit: '$12,321', thisYearProfit: '$8,500' &#125;,
            &#123; brand: 'Philips', lastYearSale: '49%', thisYearSale: '22%', lastYearProfit: '$745,232', thisYearProfit: '$650,323,' &#125;,
            &#123; brand: 'Song', lastYearSale: '17%', thisYearSale: '79%', lastYearProfit: '$643,242', thisYearProfit: '500,332' &#125;,
            &#123; brand: 'LG', lastYearSale: '52%', thisYearSale: ' 65%', lastYearProfit: '$421,132', thisYearProfit: '$150,005' &#125;,
            &#123; brand: 'Sharp', lastYearSale: '82%', thisYearSale: '12%', lastYearProfit: '$131,211', thisYearProfit: '$100,214' &#125;,
            &#123; brand: 'Panasonic', lastYearSale: '44%', thisYearSale: '45%', lastYearProfit: '$66,442', thisYearProfit: '$53,322' &#125;,
            &#123; brand: 'HTC', lastYearSale: '90%', thisYearSale: '56%', lastYearProfit: '$765,442', thisYearProfit: '$296,232' &#125;,
            &#123; brand: 'Toshiba', lastYearSale: '75%', thisYearSale: '54%', lastYearProfit: '$21,212', thisYearProfit: '$12,533' &#125;
        ];
    &#125;
&#125;
</app-code>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="sales"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th rowspan="3"&gt;Brand&lt;/th&gt;
            &lt;th colspan="4"&gt;Sale Rate&lt;/th&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th colspan="2"&gt;Sales&lt;/th&gt;
            &lt;th colspan="2"&gt;Profits&lt;/th&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th&gt;Last Year&lt;/th&gt;
            &lt;th&gt;This Year&lt;/th&gt;
            &lt;th&gt;Last Year&lt;/th&gt;
            &lt;th&gt;This Year&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-sale&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;sale.brand&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;sale.lastYearSale&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;sale.thisYearSale&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;sale.lastYearProfit&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;sale.thisYearProfit&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="footer"&gt;
        &lt;tr&gt;
            &lt;td colspan="3"&gt;Totals&lt;/td&gt;
            &lt;td&gt;$506,202&lt;/td&gt;
            &lt;td&gt;$531,020&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/colgroup']">live example.</a></p>

            <h5>Row Group</h5>
            <p>Row Grouping comes in two modes. In "subheader" mode, rows are grouped by a header row along with an optional group footer. In addition, the groups can be use with
                Expandable Rows too. On the other hand, the "rowspan" mode uses rowspans instead of a header to group rows. <i>groupRowsBy</i>
                property defines the field to use in row grouping. Multiple row grouping is available in "rowspan" mode by specifying the <i>groupRowsBy</i> as an array of fields. Sticky Row Group is avaiable with <i>pRowGroupHeader</i> directive.</p>

            <p>Example below demonstrates all the grouping alternatives. Note that data needs to be sorted for grouping, which can also be done by the table itself by speficying the sort properties.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;h5&gt;Subheader Grouping&lt;/h5&gt;
&lt;p&gt;Group customers by their representative.&lt;/p&gt;
&lt;p-table [value]="customers" sortField="representative.name" sortMode="single" [scrollable]="true" scrollHeight="400px" rowGroupMode="subheader" groupRowsBy="representative.name"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="min-width:200px"&gt;Name&lt;/th&gt;
            &lt;th style="min-width:200px"&gt;Country&lt;/th&gt;
            &lt;th style="min-width:200px"&gt;Company&lt;/th&gt;
            &lt;th style="min-width:200px"&gt;Status&lt;/th&gt;
            &lt;th style="min-width:200px"&gt;Date&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="groupheader" let-customer&gt;
        &lt;tr pRowGroupHeader&gt;
            &lt;td colspan="5"&gt;
                &lt;img [alt]="customer.representative.name" src="assets/showcase/images/demo/avatar/&#123;&#123;customer.representative.image&#125;&#125;" width="32" style="vertical-align: middle" /&gt;
                &lt;span class="font-bold ml-2"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="groupfooter" let-customer&gt;
        &lt;tr class="p-rowgroup-footer"&gt;
            &lt;td style="min-width: 80%"&gt;
                &lt;div style="text-align: right; width: 100%"&gt;Total Customers&lt;/div&gt;
            &lt;/td&gt;
            &lt;td style="width: 20%"&gt;&#123;&#123;calculateCustomerTotal(customer.representative.name)&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-customer let-rowIndex="rowIndex"&gt;
        &lt;tr&gt;
            &lt;td style="min-width:200px"&gt;
                &#123;&#123;customer.name&#125;&#125;
            &lt;/td&gt;
            &lt;td style="min-width:200px"&gt;
                &lt;img src="assets/showcase/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" width="30"&gt;
                &lt;span class="image-text"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td style="min-width:200px"&gt;
                &#123;&#123;customer.company&#125;&#125;
            &lt;/td&gt;
            &lt;td style="min-width:200px"&gt;
                &lt;span [class]="'customer-badge status-' + customer.status"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td style="min-width:200px"&gt;
                &#123;&#123;customer.date&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;

&lt;h5&gt;Expandable Row Groups&lt;/h5&gt;
&lt;p&gt;Group customers by their representative.&lt;/p&gt;
&lt;p-table [value]="customers" sortField="representative.name" sortMode="single" dataKey="representative.name" rowGroupMode="subheader" groupRowsBy="representative.name"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th&gt;Name&lt;/th&gt;
            &lt;th&gt;Country&lt;/th&gt;
            &lt;th&gt;Company&lt;/th&gt;
            &lt;th&gt;Status&lt;/th&gt;
            &lt;th&gt;Date&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="groupheader" let-customer let-rowIndex="rowIndex" let-expanded="expanded"&gt;
        &lt;tr&gt;
            &lt;td colspan="5"&gt;
                &lt;button type="button" pButton pRipple [pRowToggler]="customer" class="p-button-text p-button-rounded p-button-plain mr-2" [icon]="expanded ? 'pi pi-chevron-down' : 'pi pi-chevron-right'"&gt;&lt;/button&gt;
                &lt;img [alt]="customer.representative.name" src="assets/showcase/images/demo/avatar/&#123;&#123;customer.representative.image&#125;&#125;" width="32" style="vertical-align: middle" /&gt;
                &lt;span class="font-bold ml-2"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="groupfooter" let-customer&gt;
        &lt;tr class="p-rowgroup-footer"&gt;
            &lt;td colspan="4" style="text-align: right"&gt;Total Customers&lt;/td&gt;
            &lt;td&gt;&#123;&#123;calculateCustomerTotal(customer.representative.name)&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="rowexpansion" let-customer&gt;
        &lt;tr&gt;
            &lt;td&gt;
                &#123;&#123;customer.name&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;img src="assets/showcase/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" width="30"&gt;
                &lt;span class="image-text"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &#123;&#123;customer.company&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span [class]="'customer-badge status-' + customer.status"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &#123;&#123;customer.date&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;

&lt;h5&gt;RowSpan Grouping&lt;/h5&gt;
&lt;p-table [value]="customers" rowGroupMode="rowspan" groupRowsBy="representative.name" sortField="representative.name" sortMode="single"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="width:3rem"&gt;#&lt;/th&gt;
            &lt;th&gt;Representative&lt;/th&gt;
            &lt;th&gt;Name&lt;/th&gt;
            &lt;th&gt;Country&lt;/th&gt;
            &lt;th&gt;Company&lt;/th&gt;
            &lt;th&gt;Status&lt;/th&gt;
            &lt;th&gt;Date&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-customer let-rowIndex="rowIndex" let-rowgroup="rowgroup" let-rowspan="rowspan"&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;rowIndex&#125;&#125;&lt;/td&gt;
            &lt;td *ngIf="rowgroup" [attr.rowspan]="rowspan"&gt;
                &lt;img [alt]="customer.representative.name" src="assets/showcase/images/demo/avatar/&#123;&#123;customer.representative.image&#125;&#125;" width="32" style="vertical-align: middle" /&gt;
                &lt;span class="font-bold ml-2"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &#123;&#123;customer.name&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;img src="assets/showcase/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" width="30"&gt;
                &lt;span class="image-text"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &#123;&#123;customer.company&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span [class]="'customer-badge status-' + customer.status"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &#123;&#123;customer.date&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableRowGroupDemo implements OnInit &#123;

    customers: Customer[];

    constructor(private customerService: CustomerService) &#123; &#125;

    ngOnInit() &#123;
        this.customerService.getCustomersMedium().then(data =&gt; &#123;
            this.customers = data;
        &#125;);
    &#125;

    calculateCustomerTotal(name) &#123;
        let total = 0;

        if (this.customers) &#123;
            for (let customer of this.customers) &#123;
                if (customer.representative.name === name) &#123;
                    total++;
                &#125;
            &#125;
        &#125;

        return total;
    &#125;
&#125;
</app-code>

            <p>See the <a [routerLink]="['/table/rowgroup']">live example.</a></p>

            <h5>Paginator</h5>
            <p>Pagination is enabled by setting the <i>paginator</i> property to true. Rows property defines the number of rows per page, and pageLinks specify the the number
                of page links to display. See <a [routerLink]="['/paginator']">paginator</a> component for more information.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [paginator]="true" [rows]="10"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>Paginator can also be controlled via model using a binding to the <i>first</i> property where changes trigger a pagination.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [paginator]="true" [rows]="10" [(first)]="first"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TablePageDemo implements OnInit &#123;

    cars: Car[];

    first: number = 0;

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    &#125;

    reset() &#123;
        this.first = 0;
    &#125;
&#125;
</app-code>

            <p>To customize the paginator, use <i>paginatorLeftTemplate</i>, <i>paginatorRightTemplate</i> and <i>paginatorDropdownItemTemplate</i> templates.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [paginator]="true" [rows]="10" [first]="first"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="paginatorleft" let-state&gt;
        &#123;&#123;state.first&#125;&#125;
        &lt;button type="button" pButton icon="pi-refresh"&gt;&lt;/button&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="paginatorright"&gt;
        &lt;button type="button" pButton icon="pi-cloud-upload"&gt;&lt;/button&gt;
    &lt;/ng-template&gt;
    &lt;ng-template let-item pTemplate="paginatordropdownitem"&gt;
        &#123;&#123;item.value&#125;&#125; - per page
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>Paginator templates get the paginator state as an implicit variable that provides the following properties</p>
            <ul>
                <li>first</li>
                <li>rows</li>
                <li>page</li>
                <li>totalRecords</li>
            </ul>

            <p>See the <a [routerLink]="['/table/page']">live example.</a></p>

            <h5>Sorting</h5>
            <p>A column can be made sortable by adding the <i>pSortableColumn</i> directive whose value is the field to sort against and a sort indicator via <i>p-sortIcon</i> component. For dynamic columns,
                setting <i>pSortableColumnDisabled</i> property as true disables sorting for that particular column.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars1"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" [pSortableColumn]="col.field"&gt;
                &#123;&#123;col.header&#125;&#125;
                &lt;p-sortIcon [field]="col.field"&gt;&lt;/p-sortIcon&gt;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>Default sorting is executed on a single column, in order to enable multiple field sorting, set <i>sortMode</i> property to "multiple" and use metakey
            when clicking on another column.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="cars" sortMode="multiple"&gt;
</app-code>


            <p>In case you'd like to display the table as sorted by default initially on load, use the <i>sortField</i> - <i>sortOrder</i> properties in single mode.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars1" sortField="year"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" [pSortableColumn]="col.field"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>In multiple mode, use the <i>multiSortMeta</i> property and bind an array of SortMeta objects.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars1" sortMode="multiple" [multiSortMeta]="multiSortMeta"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" [pSortableColumn]="col.field"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>



<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
this.multiSortMeta = [];
this.multiSortMeta.push(&#123;field: 'year', order: 1&#125;);
this.multiSortMeta.push(&#123;field: 'brand', order: -1&#125;);
</app-code>


            <p>Instead of using the built-in sorting algorithm a custom sort can be attached by enabling <i>customSort</i> property and defining a sortFunction implementation. This function gets a SortEvent instance
                that provides the data to sort, <i>sortField</i>, <i>sortOrder</i> and <i>multiSortMeta</i>.
            </p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class CustomTableSortDemo implements OnInit &#123;

    cars: Car[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.cols = [
            &#123; field: 'vin', header: 'Vin' &#125;,
            &#123; field: 'year', header: 'Year' &#125;,
            &#123; field: 'brand', header: 'Brand' &#125;,
            &#123; field: 'color', header: 'Color' &#125;
        ];
    &#125;

    customSort(event: SortEvent) &#123;
        //event.data = Data to sort
        //event.mode = 'single' or 'multiple' sort mode
        //event.field = Sort field in single sort
        //event.order = Sort order in single sort
        //event.multiSortMeta = SortMeta array in multiple sort

        event.data.sort((data1, data2) => &#123;
            let value1 = data1[event.field];
            let value2 = data2[event.field];
            let result = null;

            if (value1 == null && value2 != null)
                result = -1;
            else if (value1 != null && value2 == null)
                result = 1;
            else if (value1 == null && value2 == null)
                result = 0;
            else if (typeof value1 === 'string' && typeof value2 === 'string')
                result = value1.localeCompare(value2);
            else
                result = (value1 &lt; value2) ? -1 : (value1 > value2) ? 1 : 0;

            return (event.order * result);
        &#125;);
    &#125;
&#125;
</app-code>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" (sortFunction)="customSort($event)" [customSort]="true"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" [pSortableColumn]="col.field"&gt;
                &#123;&#123;col.header&#125;&#125;
                &lt;p-sortIcon [field]="col.field"&gt;&lt;/p-sortIcon&gt;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/sort']">live example.</a></p>

            <h5>Filtering</h5>
            <p>Filtering feature provides advanced and flexible options to query the data. Core of the filtering functionality is the <i>p-columnFilter</i> component.</p>

            <h4>Data Types</h4>
            <p>ColumnFilter requires a <i>field</i> to reference a data property along with a <i>type</i> to define the built-in form element to use in filtering. Valid values for the <i>type</i> are the listed below. Each
            type displays a suitable form element such as calendar for dates. Custom filter elements are also supported with templating.</p>

            <ul>
                <li>text (default)</li>
                <li>numeric</li>
                <li>boolean</li>
                <li>date</li>
            </ul>

            <h4>Separate Row and Filter Menu</h4>
            <p>There are two possible display alternatives for the ColumnFilter component, default is creating a separate mode whereas alternative is the advanced overlay mode enabled with setting
                display as "menu".</p>

            <h6>Separate Row</h6>
            <p>Filters are displayed within a new row in this setting and appropriate filter constraints can be selected with a dropdown.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;ng-template pTemplate="header"&gt;
    &lt;tr&gt;
        &lt;th&gt;Name&lt;/th&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;th&gt;
            &lt;p-columnFilter type="text" field="name"&gt;&lt;/p-columnFilter&gt;
        &lt;/th&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
</app-code>

            <h5>Filter Menu</h5>
            <p>Filter Menu is more advanced compared to the separate row as it provides multiple rules with operator support.</p>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;ng-template pTemplate="header"&gt;
    &lt;tr&gt;
        &lt;th&gt;Name&lt;/th&gt;
        &lt;p-columnFilter type="text" field="name" display="menu"&gt;&lt;/p-columnFilter&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
</app-code>

            <h4>Match modes</h4>
            <p>Each filter data type has its own match modes. For example, "text" type displays string matchers, and startsWith and "date" type has before/after matchers. Defaults
                are defined at the <i>PrimeNGConfig</i> instance to apply matchers for a certain type in the entire application with ability to override the global configuration using the <i>matchModeOptions</i> property that accepts a SelectItem array.
                Global configuration can be changed during initialization of the root component. Here is an example that applies options for data types. Note that values used in this sample
                are the default values and this configuration is only required if you'd like to customize defaults globally.
            </p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123; Component, OnInit &#125; from '@angular/core';
import &#123; PrimeNGConfig &#125; from 'primeng/api';
import &#123; FilterMatchMode &#125; from './filtermatchmode';

@Component(&#123;
    selector: 'app-root',
    templateUrl: './app.component.html'
&#125;)
export class AppComponent implements OnInit &#123;

    constructor(private config: PrimeNGConfig) &#123;&#125;

    ngOnInit() &#123;
        this.config.filterMatchModeOptions = &#123;
            text: [
                FilterMatchMode.STARTS_WITH,
                FilterMatchMode.CONTAINS,
                FilterMatchMode.NOT_CONTAINS,
                FilterMatchMode.ENDS_WITH,
                FilterMatchMode.EQUALS,
                FilterMatchMode.NOT_EQUALS
            ],
            numeric: [
                FilterMatchMode.EQUALS,
                FilterMatchMode.NOT_EQUALS,
                FilterMatchMode.LESS_THAN,
                FilterMatchMode.LESS_THAN_OR_EQUAL_TO,
                FilterMatchMode.GREATER_THAN,
                FilterMatchMode.GREATER_THAN_OR_EQUAL_TO
            ],
            date: [
                FilterMatchMode.DATE_IS,
                FilterMatchMode.DATE_IS_NOT,
                FilterMatchMode.DATE_BEFORE,
                FilterMatchMode.DATE_AFTER
            ]
        &#125;
    &#125;
&#125;
</app-code>

            <h4>Custom Form Elements</h4>
            <p>Using templating, a custom element can be defined as the filter. The template gets the filter value and a filterCallback as parameters
                so that current filter value can be displayed on the element with support for an event of your choice to trigger the filter. In this example,
                a dropdown is used to select a value from a list.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;th&gt;
    &lt;p-columnFilter field="status" matchMode="equals"&gt;
        &lt;ng-template pTemplate="filter" let-value let-filter="filterCallback"&gt;
            &lt;p-dropdown [ngModel]="value" [options]="statuses" optionLabel="name" optionValue="value"
                (onChange)="filter($event.value)" placeholder="Select a Status" [showClear]="true"&gt;
            &lt;/p-dropdown&gt;
        &lt;/ng-template&gt;
    &lt;/p-columnFilter&gt;
&lt;/th&gt;
</app-code>


<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableFilterDemo implements OnInit &#123;

    statuses: any[];

    ngOnInit() &#123;
        this.statuses = [
            &#123;name: 'Unqualified', value: 'unqualified'&#125;,
            &#123;name: 'Qualified', value: 'qualified'&#125;,
            &#123;name: 'New', value: 'new'&#125;,
            &#123;name: 'Negotiation', value: 'negotiation'&#125;,
            &#123;name: 'Renewal', value: 'renewal'&#125;,
            &#123;name: 'Proposal', value: 'proposal'&#125;
        ]
    &#125;

&#125;
</app-code>

            <h4>Global Filtering</h4>
            <p>Global filtering searches all the fields from a single form element. In order to implement global filtering, call the <i>filterGlobal</i> of the table with a value and match mode. If you have static columns and need to use global filtering, <i>globalFilterFields</i> property must be defined to configure which fields should be used in global filtering. Another
                use case of this property is to change the fields to utilize in global filtering with dynamic columns.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table #dt [value]="cars" [globalFilterFields]="['vin','year']"&gt;
    &lt;ng-template pTemplate="caption"&gt;
    &lt;div class="flex"&gt;
        &lt;span class="p-input-icon-left ml-auto"&gt;
            &lt;i class="pi pi-search"&gt;&lt;/i&gt;
            &lt;input pInputText type="text" (input)="dt.filterGlobal($event.target.value, 'contains')" placeholder="Search keyword" /&gt;
        &lt;/span&gt;
    &lt;/div&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <h5>ColumnFilter Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>field</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Name of the property of the data to filter.</td>
                        </tr>
                        <tr>
                            <td>type</td>
                            <td>string</td>
                            <td>text</td>
                            <td>Data type to display a suitable filter and matchers.</td>
                        </tr>
                        <tr>
                            <td>display</td>
                            <td>string</td>
                            <td>row</td>
                            <td>Display mode, valid values are "row" for a separate row and "menu" for filter menu.</td>
                        </tr>
                        <tr>
                            <td>showMenu</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to show the overlay menu.</td>
                        </tr>
                        <tr>
                            <td>matchMode</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Default match mode.</td>
                        </tr>
                        <tr>
                            <td>operator</td>
                            <td>string</td>
                            <td>and</td>
                            <td>Default operator, valid values are "and" and "or".</td>
                        </tr>
                        <tr>
                            <td>showOperator</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Specifies if the operator selection is displayed.</td>
                        </tr>
                        <tr>
                            <td>showClearButton</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to display the clear button.</td>
                        </tr>
                        <tr>
                            <td>showApplyButton</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to display the apply button.</td>
                        </tr>
                        <tr>
                            <td>showMatchModes</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to display the match modes dropdown.</td>
                        </tr>
                        <tr>
                            <td>showAddButton</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to display the add a rule button.</td>
                        </tr>
                        <tr>
                            <td>hideOnClear</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether to hide the overlay on clear.</td>
                        </tr>
                        <tr>
                            <td>placeholder</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Advisory information to display on input.</td>
                        </tr>
                        <tr>
                            <td>matchModeOptions</td>
                            <td>SelectItem[]</td>
                            <td>null</td>
                            <td>Match mode options to override the global setting.</td>
                        </tr>
                        <tr>
                            <td>maxConstraints</td>
                            <td>number</td>
                            <td>2</td>
                            <td>Number of maximum constraints.</td>
                        </tr>
                        <tr>
                            <td>minFractionDigits</td>
                            <td>number</td>
                            <td>null</td>
                            <td>The minimum number of fraction digits to use for "numeric" type. Possible values are from 0 to 20; the default for plain number and percent formatting is 0; the default for currency formatting is the number of
                                minor unit digits provided by the <a href="https://www.currency-iso.org/en/home/tables/table-a1.html">ISO 4217 currency code list</a> (2 if the list doesn't provide that information).</td>
                        </tr>
                        <tr>
                            <td>maxFractionDigits</td>
                            <td>number</td>
                            <td>null</td>
                            <td>The maximum number of fraction digits to use for "numeric" type. Possible values are from 0 to 20; the default for plain
                                number formatting is the larger of minimumFractionDigits and 3; the default for currency formatting
                                is the larger of minimumFractionDigits and the number of minor unit digits provided by the <a href="https://www.currency-iso.org/en/home/tables/table-a1.html">ISO 4217 currency code list</a>
                                (2 if the list doesn't provide that information).</td>
                        </tr>
                        <tr>
                            <td>prefix</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Text to display before the value for "numeric" type.</td>
                        </tr>
                        <tr>
                            <td>suffix</td>
                            <td>string</td>
                            <td>decimal</td>
                            <td>Text to display after the value for "numeric" type.</td>
                        </tr>
                        <tr>
                            <td>locale</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Locale to be used in formatting for "numeric" type.</td>
                        </tr>
                        <tr>
                            <td>localeMatcher</td>
                            <td>string</td>
                            <td>best fit</td>
                            <td>The locale matching algorithm for "numeric" type. Possible values are "lookup" and "best fit"; the default is "best fit".
                                See <a href="https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl#Locale_negotiation">Locale Negotation</a> for details.
                            </td>
                        </tr>
                        <tr>
                            <td>currency</td>
                            <td>string</td>
                            <td>null</td>
                            <td>The currency to use in currency formatting for "numeric" type. Possible values are the <a href="https://www.currency-iso.org/en/home/tables/table-a1.html">ISO 4217 currency codes</a>,
                                such as "USD" for the US dollar, "EUR" for the euro, or "CNY" for the Chinese RMB.
                                There is no default value; if the style is "currency", the currency property must be provided.</td>
                        </tr>
                        <tr>
                            <td>currencyDisplay</td>
                            <td>string</td>
                            <td>symbol</td>
                            <td>How to display the currency in currency formatting for "numeric" type. Possible values are "symbol" to use a localized currency symbol such as €,
                                ü"code" to use the ISO currency code, "name" to use a localized currency name such as "dollar"; the default is "symbol".</td>
                        </tr>
                        <tr>
                            <td>useGrouping</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to use grouping separators for "numeric" type, such as thousands separators or thousand/lakh/crore separators.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>ColumnFilter Templates</h5>
			<div class="doc-tablewrapper">
                <table class="doc-table">
					<thead>
                        <tr>
                            <th>Name</th>
                            <th>Parameters</th>
                        </tr>
					</thead>
					<tbody>
                        <tr>
                            <td>header</td>
                            <td>-</td>
                        </tr>
                        <tr>
                            <td>filter</td>
                            <td>$implicit: filter value<br />
                                filterCallback: Callback function of the filter
                            </td>
                        </tr>
                        <tr>
                            <td>footer</td>
                            <td>-</td>
                        </tr>
					</tbody>
				</table>
            </div>

            <h4>Custom Filters</h4>
            <p>FilterService provides an API to register custom filters, Visit the <a [routerLink]="['/filterservice']">FilterService</a> documentation for more information.</p>

            <p>See the <a [routerLink]="['/table/filter']">live example.</a></p>

            <h5>Selection</h5>
            <p>Table provides built-in single and multiple selection features where selected rows are bound to the selection property and <i>onRowSelect</i> - <i>onRowUnselect</i> events
            are provided as optional callbacks. In order to enable this feature, define a <i>selectionMode</i>, bind a selection reference and add <i>pSelectableRow</i> directive
            whose value is the rowData to the rows that can be selected. Additionally, if you prefer double-click, use <i>pSelectableRowDblClick</i> directive instead and
            to disable selection events on a particular row use <i>pSelectableRowDisabled</i>. In both cases optional
            <i>pSelectableRowIndex</i> property is avaiable to access the row index. By default, each row click adds or removes the row from the selection. If you prefer a classic
            metaKey based selection approach, enable <i>metaKeySelection</i> true so that multiple selection or unselection of a row requires metaKey to be pressed. Note that, on touch enabled
            devices, metaKey based selection is turned off automatically as there is no metaKey on devices such as mobile phones.</p>

            <p>As an alternative to the row click, radiobutton or checkbox elements can be used to implement row selection.</p>

            <p>When resolving if a row is selected, by default, Table compares selection array with the datasource which may cause a performance issue with huge datasets that do not use pagination.
                If available, the fastest way is to use dataKey property that identifies a unique row so that Table can avoid comparing arrays as internally a map instance is used instead of looping arrays. On the other hand,
                if dataKey cannot be provided consider using <i>compareSelectionBy</i> property as "equals" which uses reference comparison instead of the default "deepEquals" comparison. The latter is slower since it checks all properties.
            </p>

            <p>In single mode, selection binding is an object reference.</p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableDemo implements OnInit &#123;

    cars: Car[];

    selectedCar: Car;

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    &#125;
&#125;
</app-code>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" selectionMode="single" [(selection)]="selectedCar"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr [pSelectableRow]="rowData"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

        <p>In multiple mode, selection binding should be an array. Note that if you require shiftKey based range selection, pass the rowIndex to the <i>SelectableRow</i> directive.</p>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableDemo implements OnInit &#123;

    cars: Car[];

    selectedCars: Car[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);
    &#125;
&#125;
</app-code>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" selectionMode="multiple" [(selection)]="selectedCars"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns" let-rowIndex="rowIndex"&gt;
        &lt;tr [pSelectableRow]="rowData" [pSelectableRowIndex]="rowIndex"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>Single selection using a radiobutton can be done by using <i>p-tableRadioButton</i> component.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [(selection)]="selectedCar" dataKey="vin"&gt;
&lt;ng-template pTemplate="header" let-columns&gt;
    &lt;tr&gt;
        &lt;th style="width: 2.25em"&gt;&lt;/th&gt;
        &lt;th *ngFor="let col of columns"&gt;
            &#123;&#123;col.header&#125;&#125;
        &lt;/th&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
&lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
    &lt;tr&gt;
        &lt;td&gt;
            &lt;p-tableRadioButton [value]="rowData"&gt;&lt;/p-tableRadioButton&gt;
        &lt;/td&gt;
        &lt;td *ngFor="let col of columns"&gt;
            &#123;&#123;rowData[col.field]&#125;&#125;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
&lt;ng-template pTemplate="summary"&gt;
        &lt;div style="text-align: left"&gt;
            Selected Car: &#123;&#123;selectedCar4 ? selectedCar4.vin + ' - ' + selectedCar4.brand + ' - ' + selectedCar4.year + ' - ' + selectedCar4.color: 'none'&#125;&#125;
        &lt;/div&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


                <p>Similarly <i>p-tableCheckbox</i> and <i>p-tableHeaderCheckbox</i> elements are provided to implement checkbox based multiple selection.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [(selection)]="selectedCars" dataKey="vin"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th style="width: 2.25em"&gt;
                &lt;p-tableHeaderCheckbox&gt;&lt;/p-tableHeaderCheckbox&gt;
            &lt;/th&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td&gt;
                &lt;p-tableCheckbox [value]="rowData"&gt;&lt;/p-tableCheckbox&gt;
            &lt;/td&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <p>Both <i>p-tableCheckbox</i> and <i>p-tableRadioButton</i> components can be <i>disabled</i> using their property with the same name to prevent selection of a particular row. In addition, the index of the row
                needs to be provided to the checkbox/radiobutton components so that they can be available at the <i>onRowSelect</i> or <i>onRowUnselect</i> events of the Table.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;ng-template pTemplate="body" let-rowData let-rowIndex="rowIndex" let-columns="columns"&gt;
    &lt;tr [pSelectableRow]="rowData"&gt;
        &lt;td&gt;
            &lt;p-tableCheckbox [value]="rowData" [disabled]="rowData.year > 2010" [index]="rowIndex"&gt;&lt;/p-tableCheckbox&gt;
        &lt;/td&gt;
        &lt;td *ngFor="let col of columns"&gt;
            &#123;&#123;rowData[col.field]&#125;&#125;
        &lt;/td&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
</app-code>


            <h5>TableCheckbox Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>value</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Row data value of the component.</td>
                        </tr>
                        <tr>
                            <td>index</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Index of the row.</td>
                        </tr>
                        <tr>
                            <td>disabled</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether the component is disabled.</td>
                        </tr>
                        <tr>
                            <td>inputId</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Identifier of the checkbox.</td>
                        </tr>
                        <tr>
                            <td>name</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Name of the checkbox.</td>
                        </tr>
                        <tr>
                            <td>required</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>When present, it specifies that checkbox must be checked before submitting the form.</td>
                        </tr>
                        <tr>
                            <td>ariaLabel</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Defines a string that labels the checkbox for accessibility.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>TableHeaderCheckbox Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>disabled</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether the component is disabled.</td>
                        </tr>
                        <tr>
                            <td>inputId</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Identifier of the checkbox.</td>
                        </tr>
                        <tr>
                            <td>name</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Name of the checkbox.</td>
                        </tr>
                        <tr>
                            <td>ariaLabel</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Defines a string that labels the checkbox for accessibility.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>TableRadioButton Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>value</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Row data value of the component.</td>
                        </tr>
                        <tr>
                            <td>index</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Index of the row.</td>
                        </tr>
                        <tr>
                            <td>disabled</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether the component is disabled.</td>
                        </tr>
                        <tr>
                            <td>inputId</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Identifier of the radiobutton.</td>
                        </tr>
                        <tr>
                            <td>name</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Name of the radiobutton.</td>
                        </tr>
                        <tr>
                            <td>ariaLabel</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Defines a string that labels the element for accessibility.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <p>See the <a [routerLink]="['/table/selection']">live example.</a></p>

            <h5>ContextMenu</h5>
            <p>Table has exclusive integration with <i>contextmenu</i> component. In order to attach a menu to a table, add <i>pContextMenuRow</i> directive to the rows that can be selected with context menu, define a local template
            variable for the menu and bind it to the <i>contextMenu</i> property of the table. This enables displaying the menu whenever a row is right clicked. Optional
            <i>pContextMenuRowIndex</i> property is avaiable to access the row index. A separate <i>contextMenuSelection</i>
            property is used to get a hold of the right clicked row. For dynamic columns, setting <i>pContextMenuRowDisabled</i> property as true disables context menu for that particular row.</p>


<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [(contextMenuSelection)]="selectedCar" [contextMenu]="cm"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns" let-rowIndex="rowIndex"&gt;
        &lt;tr [pContextMenuRow]="rowData" [pContextMenuRowIndex]="rowIndex"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;

&lt;p-contextMenu #cm [model]="items"&gt;&lt;/p-contextMenu&gt;
</app-code>


            <p>By default, context menu uses a different property called <i>contextMenuSelection</i> as above, this means when row selection mode is also enabled, the two properties, both selection and <i>contextMenuSelection</i> need to be maintained.
                In case you prefer to configure Table to manage the same selection property both on row click and context menu, set <i>contextMenuSelectionMode</i> as "joint". Table below has both <i>selectionMode</i> and contextMenu enabled where
                both of these features update the same selection object.</p>


<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" selectionMode="single" [(selection)]="selectedCar" [contextMenu]="cm" contextMenuSelectionMode="joint"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr [pSelectableRow]="rowData" [pContextMenuRow]="rowData"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;

&lt;p-contextMenu #cm [model]="items"&gt;&lt;/p-contextMenu&gt;
</app-code>


            <p>See the <a [routerLink]="['/table/contextmenu']">live example.</a></p>

            <h5>Cell Editing</h5>
            <p>In-cell editing is enabled by adding <i>pEditableColumn</i> directive to an editable cell that has a <i>p-cellEditor</i> helper
                component to define the input-output templates for the edit and view modes respectively.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th&gt;Vin&lt;/th&gt;
            &lt;th&gt;Year&lt;/th&gt;
            &lt;th&gt;Brand&lt;/th&gt;
            &lt;th&gt;Color&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td pEditableColumn&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;input type="text" [(ngModel)]="rowData.vin"&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.vin&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
            &lt;td pEditableColumn&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;input type="text" [(ngModel)]="rowData.year" required&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.year&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
            &lt;td pEditableColumn&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;input type="text" [(ngModel)]="rowData.brand"&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.brand&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
            &lt;td pEditableColumn&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;input type="text" [(ngModel)]="rowData.color"&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.color&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <p>If you require the edited row data, rowIndex and the selected field in the <i>onEditInit</i>, <i>onEditComplete</i>, and <i>onEditCancel</i> events, bind the row data to the <i>pEditableColumn</i> directive, the <i>rowIndex</i> <i>pEditableColumnRowIndex</i> property and field to the the field to the <i>pEditableColumnField</i> directive.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;td [pEditableColumn]="rowData" [pEditableColumnField]="'year'" [pEditableColumnRowIndex]="index"&gt;
</app-code>


            <p>When opening a cell for editing, the table will automatically focus on the first <i>input</i>, <i>textarea</i>, or <i>select</i> element inside the output template.
               If you want to override this default behavior, you can pass a custom selector for the elements to focus into the <i>pFocusCellSelector</i> directive.
               This is useful when you would like the Tab and Shift+Tab keyboard navigation to focus on buttons or custom edit controls.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;td [pFocusCellSelector]="'input, .custom-edit-control'"&gt;
</app-code>


            <h5>Row Editing</h5>
            <p>Row editing toggles the visibility of all the editors in the row at once and provides additional options to save and cancel editing. Row editing functionality
                is enabled by setting the <i>editMode</i> to "row" on table, defining a dataKey to uniquely identify a row, adding <i>pEditableRow</i> directive to the editable rows and defining the UI Controls with <i>pInitEditableRow</i>, <i>pSaveEditableRow</i> and <i>pCancelEditableRow</i> directives respectively.
            </p>
            <p>
                Save and Cancel functionality implementation is left to the page author to provide more control over the editing business logic. Example below utilizes
                a simple implementation where a row is cloned when editing is initialized and is saved or restored depending on the result of the editing. An implicit variable called "editing" is passed to the body template
                so you may come up with your own UI controls that implement editing based on your own requirements such as adding validations and styling. Note that <i>pSaveEditableRow</i> only switches the row to back view mode when there are no validation errors.
            </p>
            <p>Moreover, you may use setting <i>pEditableRowDisabled</i> property as true to disable editing for that particular row and
                in case you need to display rows in edit mode by default, use the <i>editingRowKeys</i> property which is a map whose key is the dataKey of the record where the value is any arbitrary number greater than zero.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="cars" dataKey="vin" editMode="row"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th&gt;Vin&lt;/th&gt;
            &lt;th&gt;Year&lt;/th&gt;
            &lt;th&gt;Brand&lt;/th&gt;
            &lt;th&gt;Color&lt;/th&gt;
            &lt;th style="width:8em"&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-editing="editing" let-ri="rowIndex"&gt;
        &lt;tr [pEditableRow]="rowData"&gt;
            &lt;td&gt;
                &#123;&#123;rowData.vin&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;input pInputText type="text" [(ngModel)]="rowData.year" year&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.year&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;p-dropdown [options]="brands" [(ngModel)]="rowData.brand" [style]="&#123;'width':'100%'&#125;"&gt;&lt;/p-dropdown&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.brand&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;p-cellEditor&gt;
                    &lt;ng-template pTemplate="input"&gt;
                        &lt;input pInputText type="text" [(ngModel)]="rowData.color"&gt;
                    &lt;/ng-template&gt;
                    &lt;ng-template pTemplate="output"&gt;
                        &#123;&#123;rowData.color&#125;&#125;
                    &lt;/ng-template&gt;
                &lt;/p-cellEditor&gt;
            &lt;/td&gt;
            &lt;td style="text-align:center"&gt;
                &lt;button *ngIf="!editing" pButton type="button" pInitEditableRow icon="pi pi-pencil" class="ui-button-info" (click)="onRowEditInit(rowData)"&gt;&lt;/button&gt;
                &lt;button *ngIf="editing" pButton type="button" pSaveEditableRow icon="pi pi-check" class="ui-button-success" style="margin-right: .5em" (click)="onRowEditSave(rowData)"&gt;&lt;/button&gt;
                &lt;button *ngIf="editing" pButton type="button" pCancelEditableRow icon="pi pi-times" class="ui-button-danger" (click)="onRowEditCancel(rowData, ri)"&gt;&lt;/button&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>



<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableEditDemo implements OnInit &#123;

    cars: Car[];

    brands: SelectItem[];

    clonedCars: &#123; [s: string]: Car; &#125; = &#123;&#125;;

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.brands = [
            &#123;label: 'Audi', value: 'Audi'&#125;,
            &#123;label: 'BMW', value: 'BMW'&#125;,
            &#123;label: 'Fiat', value: 'Fiat'&#125;,
            &#123;label: 'Ford', value: 'Ford'&#125;,
            &#123;label: 'Honda', value: 'Honda'&#125;,
            &#123;label: 'Jaguar', value: 'Jaguar'&#125;,
            &#123;label: 'Mercedes', value: 'Mercedes'&#125;,
            &#123;label: 'Renault', value: 'Renault'&#125;,
            &#123;label: 'VW', value: 'VW'&#125;,
            &#123;label: 'Volvo', value: 'Volvo'&#125;
        ];
    &#125;

    onRowEditInit(car: Car) &#123;
        this.clonedCars[car.vin] = &#123;...car&#125;;
    &#125;

    onRowEditSave(car: Car) &#123;
        if (car.year > 0)
            delete this.clonedCars[car.vin];
        else
            this.messageService.add(&#123;severity:'error', summary: 'Error', detail:'Year is required'&#125;);
    &#125;

    onRowEditCancel(car: Car, index: number) &#123;
        this.cars[index] = this.clonedCars[car.vin];
        delete this.clonedCars[car.vin];
    &#125;

&#125;
</app-code>


            <p>See the <a [routerLink]="['/table/edit']">live example.</a></p>

            <h5>Expandable Rows</h5>
            <p>Row expansion allows displaying detailed content for a particular row. To use this feature, add a template named rowexpansion and
                use the <i>pRowToggler</i> directive whose value is the row data instance on an element of your choice whose click event toggles the expansion. This
            enables providing your custom UI such as buttons, links and so on. Example below uses an anchor with an icon as a toggler. Setting <i>pRowTogglerDisabled</i> as true disables the toggle event for the element.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" dataKey="vin"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th style="width: 2.25em"&gt;&lt;/th&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-expanded="expanded" let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td&gt;
                &lt;a href="#" [pRowToggler]="rowData"&gt;
                    &lt;i [ngClass]="expanded ? 'pi pi-fw pi-chevron-circle-down' : 'pi pi-fw pi-chevron-circle-right'"&gt;&lt;/i&gt;
                &lt;/a&gt;
            &lt;/td&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="rowexpansion" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td [attr.colspan]="columns.length + 1"&gt;
                &lt;div class="grid p-fluid" style="font-size:16px;padding:20px"&gt;
                    &lt;div class="col-12 md-3" style="text-align:center"&gt;
                        &lt;img [attr.alt]="rowData.brand" src="assets/showcase/images/demo/car/&#123;&#123;rowData.brand&#125;&#125;.png"&gt;
                    &lt;/div&gt;
                    &lt;div class="col-12 md-9"&gt;
                        &lt;div class="grid"&gt;
                            &lt;div class="col-12"&gt;
                                &lt;b&gt;Vin:&lt;/b&gt; &#123;&#123;rowData.vin&#125;&#125;
                            &lt;/div&gt;
                            &lt;div class="col-12"&gt;
                                &lt;b&gt;Year:&lt;/b&gt; &#123;&#123;rowData.year&#125;&#125;
                            &lt;/div&gt;
                            &lt;div class="col-12"&gt;
                                &lt;b&gt;Brand:&lt;/b&gt; &#123;&#123;rowData.brand&#125;&#125;
                            &lt;/div&gt;
                            &lt;div class="col-12"&gt;
                                &lt;b&gt;Color:&lt;/b&gt; &#123;&#123;rowData.color&#125;&#125;
                            &lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <p>Multiple rows can be expanded at the same time. If you prefer a single row expansion at any time, set <i>rowExpandMode</i> property to "single". All rows
                are collapsed initially and providing <i>expandedRowKeys</i> property whose value is the dataKeys of the rows to be expanded enables rendering these rows as expanded. A dataKey must be defined
                for this feature.</p>


<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" dataKey="vin" [expandedRowKeys]="expandedRows"&gt;
   ...
&lt;/p-table&gt;
</app-code>


            <p>See the <a [routerLink]="['/table/rowexpansion']">live example.</a></p>

            <h5>Column Resize</h5>
            <p>Columns can be resized using drag drop by setting the <i>resizableColumns</i> to true. There are two resize modes: "fit", and "expand". Fit is the default one and
            the overall table width does not change when a column is resized. In "expand" mode, table width also changes along with the column width. <i>onColumnResize</i>
            is a callback that passes the resized column header as a parameter. For dynamic columns, setting <i>pResizableColumnDisabled</i> property as true disables resizing for that particular column.
            </p>


<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [resizableColumns]="true"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" pResizableColumn&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns" class="ui-resizable-column"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <p>It is important to note that when you need to change column widths, since table width is 100%, giving fixed pixel widths does not work well as browsers scale them, instead give percentage widths.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="cars" [resizableColumns]="true"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="width:20%"&gt;Vin&lt;/th&gt;
            &lt;th style="width:30%"&gt;Year&lt;/th&gt;
            &lt;th style="width:15%"&gt;Brand&lt;/th&gt;
            &lt;th style="width:35%"&gt;Color&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-car&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;car.vin&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.year&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.brand&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.color&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/colresize']">live example.</a></p>

            <h5>Column Reordering</h5>
            <p>Columns can be reordered using drag drop by setting the <i>reorderableColumns</i> to true and adding <i>pReorderableColumn</i> directive to the columns that can be dragged. Note that
                columns should be dynamic for reordering to work. For dynamic columns, setting <i>pReorderableColumnDisabled</i> property as true disables reordering for that particular column.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [reorderableColumns]="true"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" pReorderableColumn&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/colreorder']">live example.</a></p>

            <h5>Rows Reordering</h5>
            <p>Row reordering is enabled by adding <i>pReorderableRow</i> directive with a row index binding to the rows that can be reordered with drag and drop. The optional <i>pReorderableRowDisabled</i> property is available
                to disable dragging for a particular row. In addition, drag handle should get <i>pReorderableRowHandle</i> directive to specify which element is used to initiate the dragging.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th style="width:2em"&gt;&lt;/th&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns" let-index="rowIndex"&gt;
        &lt;tr [pReorderableRow]="index"&gt;
            &lt;td&gt;
                &lt;i class="pi pi-bars" pReorderableRowHandle&gt;&lt;/i&gt;
            &lt;/td&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/reorder']">live example.</a></p>

            <h5>Data Export</h5>
            <p>Table can export its data in CSV format using the built-in <i>exportCSV()</i> function. By default, all data is exported. If you'd like to export only the selection then pass a config object with <i>selectionOnly</i> property as true. Note that
                columns should be dynamic for export functionality to work, and column objects must define field/header properties.
            </p>

            <p>PDF and EXCEL export are also available using 3rd party libraries such as jspdf. Example below demonstrates how to implement all three export options.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table #dt [columns]="cols" [value]="cars" selectionMode="multiple" [(selection)]="selectedCars"&gt;
    &lt;ng-template pTemplate="caption"&gt;
        &lt;div class="ui-helper-clearfix" style="text-align: left"&gt;
            &lt;button type="button" pButton icon="pi pi-file" iconPos="left" label="CSV" (click)="dt.exportCSV()" style="margin-right: 0.5em;"&gt;&lt;/button&gt;
            &lt;button type="button" pButton icon="pi pi-file-excel" iconPos="left" label="EXCEL" (click)="exportExcel()" style="margin-right: 0.5em;" class="ui-button-success"&gt;&lt;/button&gt;
            &lt;button type="button" pButton icon="pi pi-file-pdf" iconPos="left" label="PDF" (click)="exportPdf()" class="ui-button-warning"&gt;&lt;/button&gt;
            &lt;button type="button" pButton icon="pi pi-file" iconPos="left" label="CSV - Selection Only" (click)="dt.exportCSV(&#123;selectionOnly:true&#125;)" style="float:right"&gt;&lt;/button&gt;
        &lt;/div&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr [pSelectableRow]="rowData"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>



<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableExportDemo implements OnInit &#123;

    cars: Car[];

    selectedCars: Car[];

    cols: any[];

    columns: any[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.carService.getCarsSmall().then(cars => this.cars = cars);

        this.cols = [
            &#123; field: 'vin', header: 'Vin' &#125;,
            &#123; field: 'year', header: 'Year' &#125;,
            &#123; field: 'brand', header: 'Brand' &#125;,
            &#123; field: 'color', header: 'Color' &#125;
        ];

        this.exportColumns = this.cols.map(col => (&#123;title: col.header, dataKey: col.field&#125;));
    &#125;

    exportPdf() &#123;
        import("jspdf").then(jsPDF => &#123;
            import("jspdf-autotable").then(x => &#123;
                const doc = new jsPDF.default(0,0);
                doc.autoTable(this.columns, this.cars);
                doc.save('primengTable.pdf');
            &#125;)
        &#125;)
    &#125;

    exportExcel() &#123;
        import("xlsx").then(xlsx => &#123;
            const worksheet = xlsx.utils.json_to_sheet(this.getCars());
            const workbook = &#123; Sheets: &#123; 'data': worksheet &#125;, SheetNames: ['data'] &#125;;
            const excelBuffer: any = xlsx.write(workbook, &#123; bookType: 'xlsx', type: 'array' &#125;);
            this.saveAsExcelFile(excelBuffer, "primengTable");
        &#125;);
    &#125;

    saveAsExcelFile(buffer: any, fileName: string): void &#123;
        import("file-saver").then(FileSaver => &#123;
            let EXCEL_TYPE = 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=UTF-8';
            let EXCEL_EXTENSION = '.xlsx';
            const data: Blob = new Blob([buffer], &#123;
                type: EXCEL_TYPE
            &#125;);
            FileSaver.saveAs(data, fileName + '_export_' + new Date().getTime() + EXCEL_EXTENSION);
        &#125;);
    &#125;

    getCars() &#123;
        let cars = [];
        for(let car of this.cars) &#123;
            car.year = car.year.toString();
            cars.push(car);
        &#125;
        return cars;
    &#125;
&#125;
</app-code>
            <p>See the <a [routerLink]="['/table/export']">live example.</a></p>

            <h5>Scrolling</h5>
            <p>Table supports both horizontal and vertical scrolling as well as frozen columns and rows. Additionally, <i>virtualScroll</i> mode enables dealing with large datasets by rendering data on demand during scrolling.</p>

            <p>Sample below uses vertical scrolling where headers are fixed, and data is scrollable.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [scrollable]="true" scrollHeight="200px"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <h5>Column Widths of a Scrollable Table</h5>
            <p>Scrollable table uses flex layout so there are a couple of rules to consider when adjusting the widths of columns.</p>
            <ul>
                <li>Use <i>min-width</i> in vertical scrolling only so that when there is enough space columns may grow and for smaller screens a horizontal scrollbar is displayed to provide responsive design.</li>
                <li>When horizontal scrolling is enabled, prefer <i>width</i> instead of <i>min-width</i>.</li>
                <li>In vertical scrolling only, use <i>flex</i> to disable grow and shrink while defining a initial width. When horizontal scrolling is enabled, this is not required as columns do not grow or shrink in horizontal scrolling.</li>
            </ul>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;ng-template pTemplate="header"&gt;
    &lt;tr&gt;
        &lt;th style="flex: 0 0 4rem"&gt;&lt;/th&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
&lt;ng-template pTemplate="body" let-customer let-index="rowIndex"&gt;
    &lt;tr&gt;
        &lt;td style="flex: 0 0 4rem"&gt;&lt;/td&gt;
    &lt;/tr&gt;
&lt;/ng-template&gt;
</app-code>


            <h5>Flex Scroll</h5>
            <p>In cases where viewport should adjust itself according to the table parent's height instead of a fixed viewport height, set <i>scrollHeight</i> option as <b>flex</b>. In example below,
            table is inside a Dialog where viewport size dynamically responds to the dialog size changes such as resizing or maximizing.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;button type="button" (click)="showDialog()" pButton icon="pi pi-external-link" label="View"&gt;&lt;/button&gt;
&lt;p-dialog header="Flexible ScrollHeight" [(visible)]="dialogVisible" [style]="&#123;width: '50vw'&#125;" [baseZIndex]="10000" [maximizable]="true" [modal]="true" [resizable]="true" [contentStyle]="&#123;height: '300px'&#125;"&gt;
    &lt;p-table [columns]="cols" [value]="cars1" [scrollable]="true" scrollHeight="flex"&gt;
        &lt;ng-template pTemplate="header" let-columns&gt;
            &lt;tr&gt;
                &lt;th *ngFor="let col of columns" style="min-width:200px"&gt;
                    &#123;&#123;col.header&#125;&#125;
                &lt;/th&gt;
            &lt;/tr&gt;
        &lt;/ng-template&gt;
        &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
            &lt;tr&gt;
                &lt;td *ngFor="let col of columns" style="min-width:200px"&gt;
                    &#123;&#123;rowData[col.field]&#125;&#125;
                &lt;/td&gt;
            &lt;/tr&gt;
        &lt;/ng-template&gt;
    &lt;/p-table&gt;
    &lt;p-footer&gt;
        &lt;button type="button" pButton icon="pi pi-check" (click)="dialogVisible=false" label="Yes"&gt;&lt;/button&gt;
        &lt;button type="button" pButton icon="pi pi-times" (click)="dialogVisible=false" label="No" class="ui-button-secondary"&gt;&lt;/button&gt;
    &lt;/p-footer&gt;
&lt;/p-dialog&gt;
</app-code>


            <h5>Full Page Scroll</h5>
            <p>FlexScroll can also be used for cases where scrollable viewport should be responsive with respect to the window size. See the <a [routerLink]="['/table/flexscroll']">Full Page</a> demo for an example.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;div class="page-content" style="height: calc(100vh - 149px)"&gt;
    &lt;p-table [columns]="cols" [value]="virtualCars" [scrollable]="true" [rows]="100" scrollHeight="flex"
        [virtualScroll]="true" [virtualScrollItemSize]="46" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"&gt;
        &lt;ng-template pTemplate="caption"&gt;
            Virtual Scrolling with Lazy Loading and Full Page Viewport
        &lt;/ng-template&gt;
        &lt;ng-template pTemplate="header" let-columns&gt;
            &lt;tr&gt;
                &lt;th *ngFor="let col of columns"&gt;
                    &#123;&#123;col.header&#125;&#125;
                &lt;/th&gt;
            &lt;/tr&gt;
        &lt;/ng-template&gt;
        &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
            &lt;tr style="height:46px"&gt;
                &lt;td *ngFor="let col of columns"&gt;
                    &#123;&#123;rowData[col.field]&#125;&#125;
                &lt;/td&gt;
            &lt;/tr&gt;
        &lt;/ng-template&gt;
        &lt;ng-template pTemplate="loadingbody" let-columns="columns"&gt;
            &lt;tr style="height:46px"&gt;
                &lt;td *ngFor="let col of columns"&gt;
                    &lt;div class="loading-text"&gt;&lt;/div&gt;
                &lt;/td&gt;
            &lt;/tr&gt;
        &lt;/ng-template&gt;
    &lt;/p-table&gt;
&lt;/div&gt;
</app-code>


            <h5>Horizontal Scrolling</h5>
            <p>For horizontal scrolling, it is required to set <i>scrollDirection</i> to "horizontal" and give fixed widths to columns.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="customers" [scrollable]="true" scrollDirection="horizontal"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="width:100px"&gt;Id&lt;/th&gt;
            &lt;th style="width:200px"&gt;Name&lt;/th&gt;
            &lt;th style="width:200px"&gt;Country&lt;/th&gt;
            &lt;th style="width:200px"&gt;Date&lt;/th&gt;
            &lt;th style="width:200px"&gt;Balance&lt;/th&gt;
            &lt;th style="width:200px"&gt;Company&lt;/th&gt;
            &lt;th style="width:200px"&gt;Status&lt;/th&gt;
            &lt;th style="width:200px"&gt;Activity&lt;/th&gt;
            &lt;th style="width:200px"&gt;Representative&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-customer&gt;
        &lt;tr&gt;
            &lt;td style="width:100px"&gt;&#123;&#123;customer.id&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.date&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;formatCurrency(customer.balance)&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.company&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.activity&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <h5>Horizontal and Vertical Scrolling</h5>
            <p>Set <i>scrollDirection</i> to "both" and give fixed widths to columns to scroll both ways.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="customers" [scrollable]="true" scrollDirection="both"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="width:100px"&gt;Id&lt;/th&gt;
            &lt;th style="width:200px"&gt;Name&lt;/th&gt;
            &lt;th style="width:200px"&gt;Country&lt;/th&gt;
            &lt;th style="width:200px"&gt;Date&lt;/th&gt;
            &lt;th style="width:200px"&gt;Balance&lt;/th&gt;
            &lt;th style="width:200px"&gt;Company&lt;/th&gt;
            &lt;th style="width:200px"&gt;Status&lt;/th&gt;
            &lt;th style="width:200px"&gt;Activity&lt;/th&gt;
            &lt;th style="width:200px"&gt;Representative&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-customer&gt;
        &lt;tr&gt;
            &lt;td style="width:100px"&gt;&#123;&#123;customer.id&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.date&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;formatCurrency(customer.balance)&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.company&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.activity&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <h5>Frozen Rows</h5>
            <p>Frozen rows are used to fix certain rows while scrolling, this data is defined with the <i>frozenValue</i> property.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="unlockedCustomers" [frozenValue]="lockedCustomers" [scrollable]="true" scrollHeight="400px"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th&gt;Name&lt;/th&gt;
            &lt;th&gt;Country&lt;/th&gt;
            &lt;th&gt;Company&lt;/th&gt;
            &lt;th&gt;Representative&lt;/th&gt;
            &lt;th style="flex: 0 0 4rem"&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="frozenbody" let-customer let-index="rowIndex"&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;customer.name&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;customer.company&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/td&gt;
            &lt;td style="flex: 0 0 4rem"&gt;
                &lt;button pButton pRipple type="button" [icon]="'pi pi-lock-open'" (click)="toggleLock(customer,true,index)" class="p-button-sm p-button-text"&gt;&lt;/button&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-customer let-index="rowIndex"&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;customer.name&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;customer.company&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/td&gt;
            &lt;td style="flex: 0 0 4rem"&gt;
                &lt;button pButton pRipple type="button" [icon]="'pi pi-lock'" [disabled]="lockedCustomers.length &gt;= 2" (click)="toggleLock(customer,false,index)" class="p-button-sm p-button-text"&gt;&lt;/button&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <h6>Frozen Columns</h6>
            <p>Certain columns can be frozen by using the <i>pFrozenColumn</i> directive of the table component. In addition, <i>alignFrozen</i> is available to define whether the column should
            be fixed on the left or right.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="customers" scrollDirection="both" [scrollable]="true" scrollHeight="400px" styleClass="mt-3"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="width:200px" pFrozenColumn&gt;Name&lt;/th&gt;
            &lt;th style="width:100px"&gt;Id&lt;/th&gt;
            &lt;th style="width:200px"&gt;Country&lt;/th&gt;
            &lt;th style="width:200px"&gt;Date&lt;/th&gt;
            &lt;th style="width:200px"&gt;Company&lt;/th&gt;
            &lt;th style="width:200px"&gt;Status&lt;/th&gt;
            &lt;th style="width:200px"&gt;Activity&lt;/th&gt;
            &lt;th style="width:200px"&gt;Representative&lt;/th&gt;
            &lt;th style="width:200px" alignFrozen="right" pFrozenColumn [frozen]="balanceFrozen"&gt;Balance&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;

    &lt;ng-template pTemplate="body" let-customer&gt;
        &lt;tr&gt;
            &lt;td style="width:200px" pFrozenColumn&gt;&#123;&#123;customer.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:100px"&gt;&#123;&#123;customer.id&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.date&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.company&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.activity&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/td&gt;
            &lt;td style="width:200px" alignFrozen="right"  pFrozenColumn [frozen]="balanceFrozen"&gt;&#123;&#123;formatCurrency(customer.balance)&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <h5>pFrozenColumn Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>frozen</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to use the frozen or not.</td>
                        </tr>
                        <tr>
                            <td>alignFrozen</td>
                            <td>string</td>
                            <td>left</td>
                            <td>Whether to the column should be fixed on the left or right.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Virtual Scrolling</h5>
            <p>VirtualScroller is a performant approach to handle huge data efficiently. Setting <i>virtualScroll</i> property as true and providing a <i>virtualScrollItemSize</i> in pixels
            would be enough to enable this functionality. It is also suggested to use the same <i>virtualScrollItemSize</i> value on the tr element inside the body template.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [scrollable]="true" [rows]="100" scrollHeight="250px"
    [virtualScroll]="true" [virtualScrollItemSize]="34"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr style="height:34px"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>



<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableVirtualScrollDemo implements OnInit &#123;

    cars: Car[];

    cols: any[];

    constructor(private carService: CarService) &#123;&#125;

    ngOnInit() &#123;
        this.cols = [
            &#123;field: 'vin', header: 'Vin'&#125;,
            &#123;field: 'year', header: 'Year'&#125;,
            &#123;field: 'brand', header: 'Brand'&#125;,
            &#123;field: 'color', header: 'Color'&#125;
        ];

        this.cars = Array.from(&#123;length: 10000&#125;).map(() => this.carService.generateCar());
    &#125;
&#125;
</app-code>


            <p>Example above uses preloaded data In order to load data on demand, lazy mode should be enabled where chunks of data are loaded from a datasource on scroll. To implement lazy loading,
               enable <i>lazy</i> attribute, initialize your data as a placeholder collection with a length, and finally implement a method callback using <i>onLazyLoad</i> that actually loads a chunk from a datasource. <i>onLazyLoad</i> gets a LazyLoadEvent object
               that contains information about the chunk of data to load such as the index and number of items to load. Notice that a special "loadingbody" template is available to provide feedback to the users
                about the loading status of a scroll event.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="virtualCars" [scrollable]="true" [rows]="100" scrollHeight="250px"
    [virtualScroll]="true" [virtualScrollItemSize]="34" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr style="height:34px"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="loadingbody" let-columns="columns"&gt;
        &lt;tr style="height:34px"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &lt;div class="loading-text"&gt;&lt;/div&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableVirtualScrollDemo implements OnInit &#123;

    cars: Car[];

    virtualCars: Car[];

    cols: any[];

    constructor(private carService: CarService) &#123;&#125;

    ngOnInit() &#123;
        this.cols = [
            &#123;field: 'vin', header: 'Vin'&#125;,
            &#123;field: 'year', header: 'Year'&#125;,
            &#123;field: 'brand', header: 'Brand'&#125;,
            &#123;field: 'color', header: 'Color'&#125;
        ];

        this.cars = Array.from(&#123;length: 10000&#125;).map(() => this.carService.generateCar());
        this.virtualCars = Array.from(&#123;length: 10000&#125;);
    &#125;

    loadCarsLazy(event: LazyLoadEvent) &#123;
        //simulate remote connection with a timeout
        setTimeout(() => &#123;
            //load data of required page
            let loadedCars = this.cars.slice(event.first, (event.first + event.rows));

            //populate page of virtual cars
            Array.prototype.splice.apply(this.virtualCars, [...[event.first, event.rows], ...loadedCars]);

            //trigger change detection
            event.forceUpdate();
        &#125;, Math.random() * 1000 + 250);
    &#125;
&#125;
</app-code>


            <p>See the <a [routerLink]="['/table/scroll']">scroll</a> and <a [routerLink]="['/table/virtualscroll']">virtual scroll</a> examples.</p>

            <h5>Lazy Loading</h5>
            <p>Lazy mode is handy to deal with large datasets. Instead of loading the entire data, small chunks of data is loaded by invoking
             <i>onLazyLoad</i> callback every time paging, sorting, and filtering happens. To implement lazy loading,
            enable <i>lazy</i> attribute and provide a method callback using <i>onLazyLoad</i> that actually loads the data from a remote datasource. <i>onLazyLoad</i> gets an event object
            that contains information about how the data should be loaded. It is also important to assign the logical number of rows to totalRecords by doing a projection query for paginator configuration so that paginator
            displays the UI assuming there are actually records of totalRecords size although in reality they aren't as in lazy mode, only the records that are displayed on the current page exist.</p>


<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars" [lazy]="true" (onLazyLoad)="loadCarsLazy($event)" [paginator]="true" [rows]="10" [totalRecords]="totalRecords"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                 &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
loadData(event: LazyLoadEvent) &#123;
    //event.first = First row offset
    //event.rows = Number of rows per page
    //event.sortField = Field name to sort in single sort mode
    //event.sortOrder = Sort order as number, 1 for asc and -1 for dec in single sort mode
    //multiSortMeta: An array of SortMeta objects used in multiple columns sorting. Each SortMeta has field and order properties.
    //filters: Filters object having field as key and filter value, filter matchMode as value
    //globalFilter: Value of the global filter if available
    this.cars = //do a request to a remote datasource using a service and return the cars that match the lazy load criteria
&#125;
</app-code>

            <p>See the <a [routerLink]="['/table/lazy']">live example.</a></p>

            <h5>TableState</h5>
            <p>Stateful table allows keeping the state such as page, sort, and filtering either at local storage or session storage so that when the page is visited again, table would render the data using its last settings.
                Enabling state is easy as defining a unique <i>stateKey</i>, the storage to keep the state is defined with the <i>stateStorage</i> property that accepts session for sessionStorage and local for localStorage. Currently following features
                are supported by TableState; paging, sorting, filtering, column resizing, column reordering, row expansion and row selection.
            </p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table #dt1 [columns]="cols" [value]="cars" [paginator]="true" [rows]="10" dataKey="vin" [resizableColumns]="true" [reorderableColumns]="true"
    selectionMode="single" [(selection)]="selectedCar" stateStorage="session" stateKey="statedemo-session"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" [pSortableColumn]="col.field" pResizableColumn pReorderableColumn&gt;
                &#123;&#123;col.header&#125;&#125;
                &lt;p-sortIcon [field]="col.field"&gt;&lt;/p-sortIcon&gt;
            &lt;/th&gt;
        &lt;/tr&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns" [ngSwitch]="col.field" class="ui-fluid"&gt;
                &lt;input pInputText type="text" (input)="dt1.filter($event.target.value, col.field, col.filterMatchMode)" [value]="dt1.filters[col.field]?.value"&gt;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr [pSelectableRow]="rowData"&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

            <p>See the <a [routerLink]="['/table/state']">live example.</a></p>

            <h5>EmptyMessage</h5>
            <p>When there is no data, <i>emptymessage</i> template can be used to display a message.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="cars"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                    &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="emptymessage" let-columns&gt;
        &lt;tr&gt;
            &lt;td [attr.colspan]="columns.length"&gt;
                No records found
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <h5>Loading Status</h5>
            <p>Table has a loading property, when enabled a spinner icon is displayed to indicate data load.
                An optional <i>loadingIcon</i> property can be passed in case you'd like a different loading icon.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="cars" [loading]="loading"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th&gt;Vin&lt;/th&gt;
            &lt;th&gt;Year&lt;/th&gt;
            &lt;th&gt;Brand&lt;/th&gt;
            &lt;th&gt;Color&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-car&gt;
        &lt;tr&gt;
            &lt;td&gt;&#123;&#123;car.vin&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.year&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.brand&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;car.color&#125;&#125;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>



<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
export class TableDemo implements OnInit &#123;

    loading: boolean;

    cars: Car[];

    constructor(private carService: CarService) &#123; &#125;

    ngOnInit() &#123;
        this.loading = true;
        setTimeout(() => &#123;
            this.carService.getCarsSmall().then(cars => this.cars = cars);
            this.loading = false;
        &#125;, 1000);
    &#125;
&#125;
</app-code>

            <h5>Responsive</h5>
            <p>DataTable responsive layout can be achieved in two ways: first approach is displaying a horizontal scrollbar for smaller screens,
                and the second one is defining a breakpoint to display the cells of a row as stacked. Scrollable tables use the scroll layout approach internally and do not require additional configuration.</p>

            <h5>Scroll Layout</h5>
            <p>Set <i>responsiveLayout</i> to scroll to enabled this layout. Note that when scroll mode is enabled, table-layout automatically switches to auto from fixed
            as a result table widths are likely to differ and resizable columns are not supported. Read more about <a href="https://www.w3schools.com/cssref/pr_tab_table-layout.asp">table-layout</a> for more details.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="products" responsiveLayout="scroll"&gt;

&lt;/p-table&gt;
</app-code>

            <h5>Stack Layout</h5>
            <p>In stack layout, columns are displayed as stacked after a certain breakpoint. Default is '960px'. This feature is enabled
                by setting responsiveLayout to stack and adding an element whose class name is "p-column-title" to the body cells.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [columns]="cols" [value]="customers" responsiveLayout="stack" breakpoint="680px"&gt;
    &lt;ng-template pTemplate="header" let-columns&gt;
        &lt;tr&gt;
            &lt;th *ngFor="let col of columns"&gt;
                &#123;&#123;col.header&#125;&#125;
            &lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-rowData let-columns="columns"&gt;
        &lt;tr&gt;
            &lt;td *ngFor="let col of columns"&gt;
                &lt;span class="p-column-title"&gt;&#123;&#123;col.header&#125;&#125;&lt;/span&gt;
                &#123;&#123;rowData[col.field]&#125;&#125;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

<p>See the <a [routerLink]="['/table/responsive']">live example.</a></p>
            <h5>Styling Certain Rows and Columns</h5>
            <p>Certain rows and cells can easily be styled using templating features. In example below, the row whose vin property is '123' will get the 'success' style class.</p>

<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table [value]="products"&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th&gt;Code&lt;/th&gt;
            &lt;th&gt;Name&lt;/th&gt;
            &lt;th&gt;Category&lt;/th&gt;
            &lt;th&gt;Quantity&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-product&gt;
        &lt;tr [ngClass]="&#123;'row-accessories': product.category === 'Accessories'&#125;"&gt;
            &lt;td&gt;&#123;&#123;product.code&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;product.name&#125;&#125;&lt;/td&gt;
            &lt;td&gt;&#123;&#123;product.category&#125;&#125;&lt;/td&gt;
            &lt;td&gt;
                &lt;div [ngClass]="&#123;'outofstock': product.quantity === 0, 'lowstock': (product.quantity &gt; 0 && product.quantity &lt; 10),'instock': product.quantity &gt; 10&#125;"&gt;
                    &#123;&#123;product.quantity&#125;&#125;
                &lt;/div&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>


            <p>See the <a [routerLink]="['/table/style']">live example.</a></p>

            <h5>Performance Tips</h5>
            <ul>
                <li>When selection is enabled use dataKey to avoid deep checking when comparing objects.</li>
                <li>Use rowTrackBy to avoid unnecessary dom operations.</li>
                <li>Prefer lazy loading for large datasets.</li>
            </ul>

            <h5>Properties</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Type</th>
                            <th>Default</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>value</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of objects to display.</td>
                        </tr>
                        <tr>
                            <td>columns</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of objects to represent dynamic columns.</td>
                        </tr>
                        <tr>
                            <td>frozenColumns</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of objects to represent dynamic columns that are frozen.</td>
                        </tr>
                        <tr>
                            <td>frozenValue</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of objects to display as frozen.</td>
                        </tr>
                        <tr>
                            <td>responsiveLayout</td>
                            <td>string</td>
                            <td>stack</td>
                            <td>Defines the responsive mode, valid options are "stack" and "scroll".</td>
                        </tr>
                        <tr>
                            <td>breakpoint</td>
                            <td>string</td>
                            <td>960px</td>
                            <td>The breakpoint to define the maximum width boundary when using stack responsive layout.</td>
                        </tr>
                        <tr>
                            <td>style</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Inline style of the component.</td>
                        </tr>
                        <tr>
                            <td>styleClass</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Style class of the component.</td>
                        </tr>
                        <tr>
                            <td>tableStyle</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Inline style of the table.</td>
                        </tr>
                        <tr>
                            <td>tableStyleClass</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Style class of the table.</td>
                        </tr>
                        <tr>
                            <td>paginator</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>When specified as true, enables the pagination.</td>
                        </tr>
                        <tr>
                            <td>rows</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Number of rows to display per page.</td>
                        </tr>
                        <tr>
                            <td>first</td>
                            <td>number</td>
                            <td>0</td>
                            <td>Index of the first row to be displayed.</td>
                        </tr>
                        <tr>
                            <td>totalRecords</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Number of total records, defaults to length of value when not defined.</td>
                        </tr>
                        <tr>
                            <td>pageLinks</td>
                            <td>number</td>
                            <td>null</td>
                            <td>Number of page links to display in paginator.</td>
                        </tr>
                        <tr>
                            <td>rowsPerPageOptions</td>
                            <td>array</td>
                            <td>null</td>
                            <td>Array of integer/object values to display inside rows per page dropdown of paginator</td>
                        </tr>
                        <tr>
                            <td>alwaysShowPaginator</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to show it even there is only one page.</td>
                        </tr>
                        <tr>
                            <td>showFirstLastIcon</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>When enabled, icons are displayed on paginator to go first and last page.</td>
                        </tr>
                        <tr>
                            <td>paginatorPosition</td>
                            <td>string</td>
                            <td>bottom</td>
                            <td>Position of the paginator, options are "top", "bottom" or "both".</td>
                        </tr>
                        <tr>
                            <td>currentPageReportTemplate</td>
                            <td>string</td>
                            <td>(&#123;currentPage&#125; of &#123;totalPages&#125;)</td>
                            <td>Template of the current page report element. Available placeholders are
                                &#123;currentPage&#125;,&#123;totalPages&#125;,&#123;rows&#125;,&#123;first&#125;,&#123;last&#125; and &#123;totalRecords&#125;
                            </td>
                        </tr>
                        <tr>
                            <td>showCurrentPageReport</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether to display current page report.</td>
                        </tr>
                        <tr>
                            <td>showJumpToPageDropdown</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether to display a dropdown to navigate to any page.</td>
                        </tr>
                        <tr>
                            <td>showJumpToPageInput</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether to display a input to navigate to any page.</td>
                        </tr>
                        <tr>
                            <td>showPageLinks</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to show page links.</td>
                        </tr>
                        <tr>
                            <td>sortMode</td>
                            <td>string</td>
                            <td>single</td>
                            <td>Defines whether sorting works on single column or on multiple columns.</td>
                        </tr>
                        <tr>
                            <td>sortField</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Name of the field to sort data by default.</td>
                        </tr>
                        <tr>
                            <td>sortOrder</td>
                            <td>number</td>
                            <td>1</td>
                            <td>Order to sort when default sorting is enabled.</td>
                        </tr>
                        <tr>
                            <td>multiSortMeta</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of SortMeta objects to sort the data by default in multiple sort mode.</td>
                        </tr>
                        <tr>
                            <td>rowGroupMode</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Type of the row grouping, valid values are "subheader" and "rowspan".</td>
                        </tr>
                        <tr>
                            <td>groupRowsBy</td>
                            <td>string|array</td>
                            <td>null</td>
                            <td>One or more field names to use in row grouping.</td>
                        </tr>
                        <tr>
                            <td>groupRowsByOrder</td>
                            <td>number</td>
                            <td>1</td>
                            <td>Order to sort when default row grouping is enabled.</td>
                        </tr>
                        <tr>
                            <td>defaultSortOrder</td>
                            <td>number</td>
                            <td>1</td>
                            <td>Sort order to use when an unsorted column gets sorted by user interaction.</td>
                        </tr>
                        <tr>
                            <td>customSort</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether to use the default sorting or a custom one using sortFunction.</td>
                        </tr>
                        <tr>
                            <td>showInitialSortBadge</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to use the initial sort badge or not.</td>
                        </tr>
                        <tr>
                            <td>sortFunction</td>
                            <td>function</td>
                            <td>null</td>
                            <td>A function to implement custom sorting, refer to sorting section for details.</td>
                        </tr>
                        <tr>
                            <td>selectionMode</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Specifies the selection mode, valid values are "single" and "multiple".</td>
                        </tr>
                        <tr>
                            <td>selection</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Selected row in single mode or an array of values in multiple mode.</td>
                        </tr>
                        <tr>
                            <td>selectionPageOnly</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>When enabled with paginator and checkbox selection mode, the select all checkbox in the header will select all rows on the current page.</td>
                        </tr>
                        <tr>
                            <td>selectAll</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether all data is selected.</td>
                        </tr>
                        <tr>
                            <td>contextMenuSelection</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Selected row with a context menu.</td>
                        </tr>
                        <tr>
                            <td>contextMenuSelectionMode</td>
                            <td>string</td>
                            <td>separate</td>
                            <td>Defines the behavior of context menu selection, in "separate" mode context menu updates contextMenuSelection property whereas in joint mode
                                selection property is used instead so that when row selection is enabled, both row selection and context menu selection use the same property.</td>
                        </tr>
                        <tr>
                            <td>dataKey</td>
                            <td>string</td>
                            <td>null</td>
                            <td>A property to uniquely identify a record in data.</td>
                        </tr>
                        <tr>
                            <td>metaKeySelection</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Defines whether metaKey should be considered for the selection. On touch enabled devices, metaKeySelection is turned off automatically.</td>
                        </tr>
                        <tr>
                            <td>rowSelectable</td>
                            <td>function</td>
                            <td>null</td>
                            <td>A function to decide whether a row is selectable. Need to return boolean value.<br/>
                                event.data: Field data.<br>
                                event.index: Index of data.
                            </td>
                        </tr>
                        <tr>
                            <td>rowTrackBy</td>
                            <td>Function</td>
                            <td>null</td>
                            <td>Function to optimize the dom operations by delegating to ngForTrackBy, default algoritm checks for object identity.</td>
                        </tr>
                        <tr>
                            <td>lazy</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Defines if data is loaded and interacted with in lazy manner.</td>
                        </tr>
                        <tr>
                            <td>lazyLoadOnInit</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to call lazy loading on initialization.</td>
                        </tr>
                        <tr>
                            <td>compareSelectionBy</td>
                            <td>string</td>
                            <td>deepEquals</td>
                            <td>Algorithm to define if a row is selected, valid values are "equals" that compares by reference and "deepEquals" that compares all fields.</td>
                        </tr>
                        <tr>
                            <td>csvSeparator</td>
                            <td>string</td>
                            <td>,</td>
                            <td>Character to use as the csv separator.</td>
                        </tr>
                        <tr>
                            <td>exportFilename</td>
                            <td>string</td>
                            <td>download</td>
                            <td>Name of the exported file.</td>
                        </tr>
                        <tr>
                            <td>filters</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of FilterMetadata objects to provide external filters.</td>
                        </tr>
                        <tr>
                            <td>filterDelay</td>
                            <td>number</td>
                            <td>300</td>
                            <td>Delay in milliseconds before filtering the data.</td>
                        </tr>
                        <tr>
                            <td>globalFilterFields</td>
                            <td>array</td>
                            <td>null</td>
                            <td>An array of fields as string to use in global filtering.</td>
                        </tr>
                        <tr>
                            <td>filterLocale</td>
                            <td>string</td>
                            <td>undefined</td>
                            <td>Locale to use in filtering. The default locale is the host environment's current locale.</td>
                        </tr>
                        <tr>
                            <td>expandedRowKeys</td>
                            <td>&#123;[s: string]: boolean;&#125;</td>
                            <td>null</td>
                            <td>Map instance to keep the expanded rows where key of the map is the data key of the row.</td>
                        </tr>
                        <tr>
                            <td>rowExpandMode</td>
                            <td>string</td>
                            <td>multiple</td>
                            <td>Whether multiple rows can be expanded at any time. Valid values are "multiple" and "single".</td>
                        </tr>
                        <tr>
                            <td>scrollable</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>When specifies, enables horizontal and/or vertical scrolling.</td>
                        </tr>
                        <tr>
                            <td>scrollDirection</td>
                            <td>string</td>
                            <td>vertical</td>
                            <td>Orientation of the scrolling, options are "vertical", "horizontal" and "both".</td>
                        </tr>
                        <tr>
                            <td>scrollHeight</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Height of the scroll viewport in fixed pixels or the "flex" keyword for a dynamic size.</td>
                        </tr>
                        <tr>
                            <td>virtualScroll</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether the data should be loaded on demand during scroll.</td>
                        </tr>
                        <tr>
                            <td>virtualScrollDelay</td>
                            <td>number</td>
                            <td>250</td>
                            <td>Threshold in milliseconds to delay lazy loading during scrolling.</td>
                        </tr>
                        <tr>
                            <td>virtualScrollItemSize</td>
                            <td>number</td>
                            <td>28</td>
                            <td>Height of a row to use in calculations of virtual scrolling.</td>
                        </tr>
                        <tr>
                            <td>virtualScrollOptions</td>
                            <td>ScrollerOptions</td>
                            <td>false</td>
                            <td>Whether to use the scroller feature. The properties of <a href="#" [routerLink]="['/theming']">scroller</a> component can be used like an object in it.</td>
                        </tr>
                        <tr>
                            <td>contextMenu</td>
                            <td>ContextMenu</td>
                            <td>null</td>
                            <td>Local ng-template varilable of a ContextMenu.</td>
                        </tr>
                        <tr>
                            <td>resizableColumns</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>When enabled, columns can be resized using drag and drop.</td>
                        </tr>
                        <tr>
                            <td>columnResizeMode</td>
                            <td>string</td>
                            <td>fit</td>
                            <td>Defines whether the overall table width should change on column resize, valid values are "fit" and "expand".</td>
                        </tr>
                        <tr>
                            <td>reorderableColumns</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>When enabled, columns can be reordered using drag and drop.</td>
                        </tr>
                        <tr>
                            <td>loading</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Displays a loader to indicate data load is in progress.</td>
                        </tr>
                        <tr>
                            <td>loadingIcon</td>
                            <td>string</td>
                            <td>pi-circle-o-notch</td>
                            <td>The icon to show while indicating data load is in progress.</td>
                        </tr>
                        <tr>
                            <td>showLoader</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>Whether to show the loading mask when loading property is true.</td>
                        </tr>
                        <tr>
                            <td>rowHover</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Adds hover effect to rows without the need for selectionMode. Note that tr elements that can be hovered need to have "p-selectable-row" class for rowHover to work.</td>
                        </tr>
                        <tr>
                            <td>paginatorDropdownAppendTo</td>
                            <td>any</td>
                            <td>null</td>
                            <td>Target element to attach the paginator dropdown overlay, valid values are "body" or a local ng-template variable of another element (note: use binding with brackets for template variables, e.g. [appendTo]="mydiv" for a div element having #mydiv as variable name).</td>
                        </tr>
                        <tr>
                            <td>paginatorDropdownScrollHeight</td>
                            <td>string</td>
                            <td>200px</td>
                            <td>Paginator dropdown height of the viewport in pixels, a scrollbar is defined if height of list exceeds this value.</td>
                        </tr>
                        <tr>
                            <td>autoLayout</td>
                            <td>boolean</td>
                            <td>false</td>
                            <td>Whether the cell widths scale according to their content or not.</td>
                        </tr>
                        <tr>
                            <td>resetPageOnSort</td>
                            <td>boolean</td>
                            <td>true</td>
                            <td>When true, resets paginator to first page after sorting. Available only when sortMode is set to single.</td>
                        </tr>
                        <tr>
                            <td>exportFunction</td>
                            <td>function</td>
                            <td>null</td>
                            <td>A function to implement custom export. Need to return string value.<br/>
                                event.data: Field data.<br>
                                event.field: Column field.
                            </td>
                        </tr>
                        <tr>
                            <td>stateKey</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Unique identifier of a stateful table to use in state storage.</td>
                        </tr>
                        <tr>
                            <td>stateStorage</td>
                            <td>string</td>
                            <td>session</td>
                            <td>Defines where a stateful table keeps its state, valid values are "session" for sessionStorage and "local" for localStorage.</td>
                        </tr>
                        <tr>
                            <td>editMode</td>
                            <td>string</td>
                            <td>cell</td>
                            <td>Defines the editing mode, valid values are "cell" and "row".</td>
                        </tr>
                        <tr>
                            <td>editingRowKeys</td>
                            <td>&#123;[s: string]: boolean;&#125;</td>
                            <td>null</td>
                            <td>Map instance to keep the rows being edited where key of the map is the data key of the row.</td>
                        </tr>
                        <tr>
                            <td>exportHeader</td>
                            <td>string</td>
                            <td>null</td>
                            <td>Custom export header of the column to be exported as CSV.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Events</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                    <tr>
                        <th>Name</th>
                        <th>Parameters</th>
                        <th>Description</th>
                    </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>onRowSelect</td>
                            <td>event.originalEvent: Browser event <br>
                                event.data: Selected row data or an array if a range is selected with shift key <br >
                                event.type: Type of selection, valid values are "row", "radiobutton" and "checkbox"<br>
                                event.index: Index of the row
                            </td>
                            <td>Callback to invoke when a row is selected.</td>
                        </tr>
                        <tr>
                            <td>onRowUnselect</td>
                            <td>event.originalEvent: Browser event <br>
                                event.data: Unselected data <br >
                                event.type: Type of unselection, valid values are "row" and "checkbox"<br>
                                event.index: Index of the row</td>
                            <td>Callback to invoke when a row is unselected.</td>
                        </tr>
                        <tr>
                            <td>onPage</td>
                            <td>event.first: Index of first record in page<br>
                                event.rows: Number of rows on the page</td>
                            <td>Callback to invoke when pagination occurs.</td>
                        </tr>
                        <tr>
                            <td>onSort</td>
                            <td>event.field: Field name of the sorted column<br>
                                event.order: Sort order as 1 or -1<br>
                                event.multisortmeta: Sort metadata in multi sort mode. See multiple sorting section for the structure of this object.</td>
                            <td>Callback to invoke when a column gets sorted.</td>
                        </tr>
                        <tr>
                            <td>onFilter</td>
                            <td>event.filters: Filters object having a field as the property key and an object with value, matchMode as the property value.<br>
                                event.filteredValue: Filtered data after running the filtering.</td>
                            <td>Callback to invoke when data is filtered.</td>
                        </tr>
                        <tr>
                            <td>onLazyLoad</td>
                            <td>event.first = First row offset <br>
                                event.last: Last index of the new data range to be loaded <br>
                                event.rows = Number of rows per page <br>
                                event.sortField = Field name to sort with <br>
                                event.sortOrder = Sort order as number, 1 for asc and -1 for dec <br>
                                event.multiSortMeta: An array of SortMeta objects used in multiple columns sorting. Each SortMeta has field and order properties.<br>
                                event.filters: FilterMetadata object having field as key and filter value, filter matchMode as value<br>
                                event.globalFilter: Value of the global filter if available</td>
                            <td>Callback to invoke when paging, sorting or filtering happens in lazy mode.</td>
                        </tr>
                        <tr>
                            <td>onRowExpand</td>
                            <td>event.originalEvent: Browser event<br>
                                data: Row data to expand.</td>
                            <td>Callback to invoke when a row is expanded.</td>
                        </tr>
                        <tr>
                            <td>onRowCollapse</td>
                            <td>event.originalEvent: Browser event<br>
                                data: Row data to collapse.</td>
                            <td>Callback to invoke when a row is collapsed.</td>
                        </tr>
                        <tr>
                            <td>onContextMenuSelect</td>
                            <td>event.originalEvent: Browser event <br>
                                event.data: Selected data</td>
                            <td>Callback to invoke when a row is selected with right click.</td>
                        </tr>
                        <tr>
                            <td>onColResize</td>
                            <td>event.element: Resized column header <br>
                                event.delta: Change of width in number of pixels</td>
                            <td>Callback to invoke when a column is resized.</td>
                        </tr>
                        <tr>
                            <td>onColReorder</td>
                            <td>event.dragIndex: Index of the dragged column <br>
                                event.dropIndex: Index of the dropped column <br>
                                event.columns: Columns array after reorder</td>
                            <td>Callback to invoke when a column is reordered.</td>
                        </tr>
                        <tr>
                            <td>onRowReorder</td>
                            <td>event.dragIndex: Index of the dragged row<br>
                                event.dropIndex: Index of the drop location</td>
                            <td>Callback to invoke when a row is reordered.</td>
                        </tr>
                        <tr>
                            <td>onEditInit</td>
                            <td>event.field: Column object of the cell<br>
                                event.data: Row data</td>
                            <td>Callback to invoke when a cell switches to edit mode.</td>
                        </tr>
                        <tr>
                            <td>onEditComplete</td>
                            <td>event.field: Column object of the cell<br>
                                event.data: Row data <br />
                                event.originalEvent: Browser event</td>
                            <td>Callback to invoke when cell edit is completed.</td>
                        </tr>
                        <tr>
                            <td>onEditCancel</td>
                            <td>event.field: Column object of the cell<br>
                                event.data: Row data <br />
                                event.originalEvent: Browser event</td>
                            <td>Callback to invoke when cell edit is cancelled with escape key.</td>
                        </tr>
                        <tr>
                            <td>onHeaderCheckboxToggle</td>
                            <td>event.originalEvent: Browser event <br>
                                event.checked: State of the header checkbox</td>
                            <td>Callback to invoke when state of header checkbox changes.</td>
                        </tr>
                        <tr>
                            <td>onStateSave</td>
                            <td>state: Table state</td>
                            <td>Callback to invoke table state is saved.</td>
                        </tr>
                        <tr>
                            <td>onStateRestore</td>
                            <td>state: Table state</td>
                            <td>Callback to invoke table state is restored.</td>
                        </tr>
                        <tr>
                            <td>selectAllChange</td>
                            <td>event.originalEvent: Browser event <br>
                                event.checked: State of the header checkbox</td>
                            <td>Callback to invoke when all data is selected. The selectAll property is required for this callback.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Methods</h5>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>Parameters</th>
                            <th>Description</th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>reset</td>
                            <td>-</td>
                            <td>Resets sort, filter and paginator state.</td>
                        </tr>
                        <tr>
                            <td>clearState</td>
                            <td>-</td>
                            <td>Clears table state.</td>
                        </tr>
                        <tr>
                            <td>exportCSV</td>
                            <td>config?.selectionOnly: Exports only the selection.</td>
                            <td>Exports the data in csv format.</td>
                        </tr>
                        <tr>
                            <td>resetScrollTop</td>
                            <td>-</td>
                            <td>Resets the scrollable table scroll position to the beginning.</td>
                        </tr>
                        <tr>
                            <td>scrollToVirtualIndex</td>
                            <td>index</td>
                            <td>Scrolls to the row with the given index when virtual scrolling is enabled.</td>
                        </tr>
                        <tr>
                            <td>scrollTo</td>
                            <td>options.left: Specifies the number of pixels along the X axis<br/>
                                options.top: Specifies the number of pixels along the Y axis<br />
                                options.behavior: Specifies whether the scrolling should animate smoothly (smooth), or happen instantly in a single jump (auto).</td>
                            <td>Scrolls to a position of a scrollable table viewport.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Styling</h5>
            <p>Following is the list of structural style classes, for theming classes visit <a href="#" [routerLink]="['/theming']">theming page</a>.</p>
            <div class="doc-tablewrapper">
                <table class="doc-table">
                    <thead>
                    <tr>
                        <th>Name</th>
                        <th>Element</th>
                    </tr>
                    </thead>
                    <tbody>
                        <tr>
                            <td>p-datatable</td>
                            <td>Container element.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-header</td>
                            <td>Header section.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-footer</td>
                            <td>Footer section.</td>
                        </tr>
                        <tr>
                            <td>p-sortable-column</td>
                            <td>Sortable column header.</td>
                        </tr>
                        <tr>
                            <td>p-editable-column</td>
                            <td>Editable column cell.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-thead</td>
                            <td>Thead element of header columns.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-tbody</td>
                            <td>Tbody element of body rows.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-tfoot</td>
                            <td>Tfoot element of footer columns.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-scrollable</td>
                            <td>Container element when scrolling is enabled.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-scrollable-frozen</td>
                            <td>Container element of a frozen view.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-scrollable-header</td>
                            <td>Container of header in a scrollable table.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-scrollable-body</td>
                            <td>Container of body in a scrollable table.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-scrollable-footer</td>
                            <td>Container of footer in a scrollable table.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-resizable</td>
                            <td>Container element when column resizing is enabled.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-resizable-fit</td>
                            <td>Container element when column resizing is enabled and set to fit mode.</td>
                        </tr>
                        <tr>
                            <td>p-column-resizer-helper</td>
                            <td>Vertical resizer indicator bar.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-reorderablerow-handle</td>
                            <td>Handle element of a reorderable row.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-reorder-indicator-up</td>
                            <td>Up indicator to display during column reordering.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-reorder-indicator-up</td>
                            <td>Down indicator to display during column reordering.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-loading-overlay</td>
                            <td>Overlay to display when table is loading.</td>
                        </tr>
                        <tr>
                            <td>p-datatable-loading-icon</td>
                            <td>Icon to display when table is loading.</td>
                        </tr>
                    </tbody>
                </table>
            </div>

            <h5>Dependencies</h5>
            <p>None.</p>
        </p-tabPanel>

        <p-tabPanel header="Source">
            <a href="https://github.com/primefaces/primeng/tree/master/src/app/showcase/components/table" class="btn-viewsource" target="_blank">
                <span>View on GitHub</span>
            </a>
            <a href="https://stackblitz.com/edit/primeng-tabledoc-demo" class="btn-viewsource" style="margin-left: .5em;" target="_blank">
                <span>Edit in StackBlitz</span>
            </a>
<app-code lang="markup" ngNonBindable ngPreserveWhitespaces>
&lt;p-table #dt [value]="customers" [(selection)]="selectedCustomers" dataKey="id" styleClass="p-datatable-customers" [rowHover]="true"
    [rows]="10" [showCurrentPageReport]="true" [rowsPerPageOptions]="[10,25,50]" [loading]="loading" responsiveLayout="scroll"
    [paginator]="true" currentPageReportTemplate="Showing &#123;first&#125; to &#123;last&#125; of &#123;totalRecords&#125; entries"
    [filterDelay]="0" [globalFilterFields]="['name','country.name','representative.name','status']"&gt;
    &lt;ng-template pTemplate="caption"&gt;
        &lt;div class="table-header"&gt;
            List of Customers
            &lt;span class="p-input-icon-left"&gt;
                &lt;i class="pi pi-search"&gt;&lt;/i&gt;
                &lt;input pInputText type="text" (input)="dt.filterGlobal($event.target.value, 'contains')" placeholder="Global Search" /&gt;
            &lt;/span&gt;
        &lt;/div&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="header"&gt;
        &lt;tr&gt;
            &lt;th style="width: 3rem"&gt;
                &lt;p-tableHeaderCheckbox&gt;&lt;/p-tableHeaderCheckbox&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="name"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Name
                    &lt;p-sortIcon field="name"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter type="text" field="name" display="menu" class="ml-auto"&gt;&lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="country.name"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Country
                    &lt;p-sortIcon field="country.name"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter type="text" field="country.name" display="menu" class="ml-auto"&gt;&lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="representative.name"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Agent
                    &lt;p-sortIcon field="representative.name"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter field="representative" matchMode="in" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false" class="ml-auto"&gt;
                        &lt;ng-template pTemplate="header"&gt;
                            &lt;div class="px-3 pt-3 pb-0"&gt;
                                &lt;span class="font-bold"&gt;Agent Picker&lt;/span&gt;
                            &lt;/div&gt;
                        &lt;/ng-template&gt;
                        &lt;ng-template pTemplate="filter" let-value let-filter="filterCallback"&gt;
                            &lt;p-multiSelect [ngModel]="value" [options]="representatives" placeholder="Any" (onChange)="filter($event.value)" optionLabel="name"&gt;
                                &lt;ng-template let-option pTemplate="item"&gt;
                                    &lt;div class="p-multiselect-representative-option"&gt;
                                        &lt;img [alt]="option.label" src="assets/showcase/images/demo/avatar/&#123;&#123;option.image&#125;&#125;" width="32" style="vertical-align: middle" /&gt;
                                        &lt;span class="ml-1"&gt;&#123;&#123;option.name&#125;&#125;&lt;/span&gt;
                                    &lt;/div&gt;
                                &lt;/ng-template&gt;
                            &lt;/p-multiSelect&gt;
                        &lt;/ng-template&gt;
                    &lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="date"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Date
                    &lt;p-sortIcon field="date"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter type="date" field="date" display="menu" class="ml-auto"&gt;&lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="balance"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Balance
                    &lt;p-sortIcon field="balance"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter type="numeric" field="balance" display="menu" currency="USD" class="ml-auto"&gt;&lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="status"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Status
                    &lt;p-sortIcon field="status"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter field="status" matchMode="equals" display="menu" class="ml-auto"&gt;
                        &lt;ng-template pTemplate="filter" let-value let-filter="filterCallback"&gt;
                            &lt;p-dropdown [ngModel]="value" [options]="statuses" (onChange)="filter($event.value)" placeholder="Any"&gt;
                                &lt;ng-template let-option pTemplate="item"&gt;
                                    &lt;span [class]="'customer-badge status-' + option.value"&gt;&#123;&#123;option.label&#125;&#125;&lt;/span&gt;
                                &lt;/ng-template&gt;
                            &lt;/p-dropdown&gt;
                        &lt;/ng-template&gt;
                    &lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th pSortableColumn="activity"&gt;
                &lt;div class="flex justify-content-between align-items-center"&gt;
                    Activity
                    &lt;p-sortIcon field="activity"&gt;&lt;/p-sortIcon&gt;
                    &lt;p-columnFilter field="activity" matchMode="between" display="menu" [showMatchModes]="false" [showOperator]="false" [showAddButton]="false" class="ml-auto"&gt;
                        &lt;ng-template pTemplate="filter" let-filter="filterCallback"&gt;
                            &lt;p-slider [ngModel]="activityValues" [range]="true" (onSlideEnd)="filter($event.values)" styleClass="m-3"&gt;&lt;/p-slider&gt;
                            &lt;div class="flex align-items-center justify-content-between px-2"&gt;
                                &lt;span&gt;&#123;&#123;activityValues[0]&#125;&#125;&lt;/span&gt;
                                &lt;span&gt;&#123;&#123;activityValues[1]&#125;&#125;&lt;/span&gt;
                            &lt;/div&gt;
                        &lt;/ng-template&gt;
                    &lt;/p-columnFilter&gt;
                &lt;/div&gt;
            &lt;/th&gt;
            &lt;th style="width: 8rem"&gt;&lt;/th&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="body" let-customer&gt;
        &lt;tr class="p-selectable-row"&gt;
            &lt;td&gt;
                &lt;p-tableCheckbox [value]="customer"&gt;&lt;/p-tableCheckbox&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span class="p-column-title"&gt;Name&lt;/span&gt;
                &#123;&#123;customer.name&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span class="p-column-title"&gt;Country&lt;/span&gt;
                &lt;img src="assets/showcase/images/demo/flag/flag_placeholder.png" [class]="'flag flag-' + customer.country.code" width="30"&gt;
                &lt;span class="image-text"&gt;&#123;&#123;customer.country.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span class="p-column-title"&gt;Representative&lt;/span&gt;
                &lt;img [alt]="customer.representative.name" src="assets/showcase/images/demo/avatar/&#123;&#123;customer.representative.image&#125;&#125;" width="32" style="vertical-align: middle" /&gt;
                &lt;span class="image-text"&gt;&#123;&#123;customer.representative.name&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span class="p-column-title"&gt;Date&lt;/span&gt;
                &#123;&#123;customer.date | date: 'MM/dd/yyyy'&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &#123;&#123;customer.balance | currency:'USD':'symbol'&#125;&#125;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span class="p-column-title"&gt;Status&lt;/span&gt;
                &lt;span [class]="'customer-badge status-' + customer.status"&gt;&#123;&#123;customer.status&#125;&#125;&lt;/span&gt;
            &lt;/td&gt;
            &lt;td&gt;
                &lt;span class="p-column-title"&gt;Activity&lt;/span&gt;
                &lt;p-progressBar [value]="customer.activity" [showValue]="false"&gt;&lt;/p-progressBar&gt;
            &lt;/td&gt;
            &lt;td style="text-align: center"&gt;
                &lt;button pButton type="button" class="p-button-secondary" icon="pi pi-cog"&gt;&lt;/button&gt;
            &lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
    &lt;ng-template pTemplate="emptymessage"&gt;
        &lt;tr&gt;
            &lt;td colspan="8"&gt;No customers found.&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/ng-template&gt;
&lt;/p-table&gt;
</app-code>

<app-code lang="typescript" ngNonBindable ngPreserveWhitespaces>
import &#123; Component, OnInit &#125; from '@angular/core';
import &#123; Customer, Representative &#125; from '../../domain/customer';
import &#123; CustomerService &#125; from '../../service/customerservice';

@Component(&#123;
    templateUrl: './tabledemo.html',
    styleUrls: ['./tabledemo.scss']
&#125;)
export class TableDemo implements OnInit &#123;

    customers: Customer[];

    selectedCustomers: Customer[];

    representatives: Representative[];

    statuses: any[];

    loading: boolean = true;

    activityValues: number[] = [0, 100];

    constructor(private customerService: CustomerService) &#123; &#125;

    ngOnInit() &#123;
        this.customerService.getCustomersLarge().then(customers => &#123;
            this.customers = customers;
            this.loading = false;

            this.customers.forEach(customer => customer.date = new Date(customer.date));
        &#125;);

        this.representatives = [
            &#123;name: "Amy Elsner", image: 'amyelsner.png'&#125;,
            &#123;name: "Anna Fali", image: 'annafali.png'&#125;,
            &#123;name: "Asiya Javayant", image: 'asiyajavayant.png'&#125;,
            &#123;name: "Bernardo Dominic", image: 'bernardodominic.png'&#125;,
            &#123;name: "Elwin Sharvill", image: 'elwinsharvill.png'&#125;,
            &#123;name: "Ioni Bowcher", image: 'ionibowcher.png'&#125;,
            &#123;name: "Ivan Magalhaes",image: 'ivanmagalhaes.png'&#125;,
            &#123;name: "Onyama Limba", image: 'onyamalimba.png'&#125;,
            &#123;name: "Stephen Shaw", image: 'stephenshaw.png'&#125;,
            &#123;name: "Xuxue Feng", image: 'xuxuefeng.png'&#125;
        ];

        this.statuses = [
            &#123;label: 'Unqualified', value: 'unqualified'&#125;,
            &#123;label: 'Qualified', value: 'qualified'&#125;,
            &#123;label: 'New', value: 'new'&#125;,
            &#123;label: 'Negotiation', value: 'negotiation'&#125;,
            &#123;label: 'Renewal', value: 'renewal'&#125;,
            &#123;label: 'Proposal', value: 'proposal'&#125;
        ]
    &#125;
&#125;
</app-code>

        </p-tabPanel>
        <p-tabPanel header="StackBlitz">
            <ng-template pTemplate="content">
                <iframe src="https://stackblitz.com/edit/primeng-tabledoc-demo?embed=1" style="width: 100%; height: 768px; border: none;"></iframe>
            </ng-template>
        </p-tabPanel>
    </p-tabView>
</div>
